From ae861e78d2e2682efeede61d6b19b0a80d7f8230 Mon Sep 17 00:00:00 2001 From: Alberto Balbo Date: Wed, 28 Jan 2026 11:37:40 +0100 Subject: [PATCH] Implementate strategie avanzate e tracking aste v1.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Aggiunto BidStrategyService: adaptive latency, jitter, offset dinamico, heat metric, soft retreat, probabilistic bidding, profiling avversari, bankroll manager. - Esteso AuctionInfo con metriche avanzate: latenze, collisioni, heat, duello, tracking sessione, override strategie. - Nuova sezione "Strategie Avanzate" in Settings (UI) con opzioni dettagliate e bulk update. - Miglioramenti UX: auto-scroll log, filtri e dettagli avanzati in Statistics, gestione nomi prodotti, pulsanti sempre attivi. - Fix bug Blazor (layout, redirect, log, conteggio puntate, entità HTML). - Aggiornata documentazione, changelog, guide Docker/Gitea. - Versione incrementata a 1.3.0. Migrazione database per nuove metriche e tracking completo. --- Mimante/AutoBidder.csproj | 10 +- Mimante/CHANGELOG.md | 767 ------------------ Mimante/CONFIGURAZIONE_FINALE.md | 296 ------- Mimante/DOCKER_DEPLOY.md | 104 --- Mimante/DOCKER_PUBLISH_GUIDE.md | 505 ------------ Mimante/FIX_DEFINITIVO_PORTA_v1.1.2.md | 272 ------- Mimante/FIX_ERRORE_NAVIGATION_E_EMOJI.md | 309 ------- Mimante/FIX_ERRORE_SECTION_REGISTRY.md | 402 --------- Mimante/FIX_HEADERS_READ_ONLY_LOGIN.md | 386 --------- Mimante/FIX_LAYOUT_LOGIN_PULITO.md | 408 ---------- Mimante/FIX_LOGIN_NON_APPARE.md | 241 ------ .../FIX_NAVIGATION_EXCEPTION_DEFINITIVO.md | 418 ---------- Mimante/FIX_PORTA_CONTAINER.md | 304 ------- Mimante/Models/AuctionInfo.cs | 169 ++++ Mimante/Models/ProductStatisticsRecord.cs | 58 ++ Mimante/NUOVO_WORKFLOW_RIEPILOGO.md | 250 ------ Mimante/PROBLEMA_HTTPS_RISOLTO.md | 274 ------- Mimante/PROBLEMA_RISOLTO.md | 214 ----- Mimante/Pages/Index.razor | 44 +- Mimante/Pages/Index.razor.cs | 64 +- Mimante/Pages/Settings.razor | 318 +++++++- Mimante/Pages/Statistics.razor | 391 ++++++++- Mimante/Program.cs | 4 +- Mimante/QUICKSTART_SECURITY.md | 211 ----- Mimante/RELEASE_v1.1.1.md | 280 ------- Mimante/RIEPILOGO_COMPLETO_FINALE.md | 289 ------- Mimante/RIEPILOGO_RELEASE_v1.1.0.md | 376 --------- Mimante/RIEPILOGO_SICUREZZA_v1.2.0.md | 427 ---------- Mimante/RIMOZIONE_CREDENZIALI_BIDOO.md | 261 ------ Mimante/SECURITY.md | 411 ---------- Mimante/Services/AuctionMonitor.cs | 358 ++++++-- Mimante/Services/BidStrategyService.cs | 501 ++++++++++++ Mimante/Services/BidooBrowserService.cs | 8 +- Mimante/Services/DatabaseService.cs | 307 +++++++ Mimante/UNRAID_TEMPLATE.md | 410 ---------- Mimante/Utilities/SettingsManager.cs | 244 ++++++ Mimante/VERIFICA_CONFIGURAZIONE_GITEA.md | 183 ----- Mimante/VERSIONING.md | 305 ------- Mimante/VERSIONING_IMPLEMENTATO.md | 340 -------- Mimante/wwwroot/css/animations.css | 6 +- Mimante/wwwroot/css/app-wpf.css | 47 +- Mimante/wwwroot/js/log-scroll.js | 15 + 42 files changed, 2382 insertions(+), 8805 deletions(-) delete mode 100644 Mimante/CHANGELOG.md delete mode 100644 Mimante/CONFIGURAZIONE_FINALE.md delete mode 100644 Mimante/DOCKER_DEPLOY.md delete mode 100644 Mimante/DOCKER_PUBLISH_GUIDE.md delete mode 100644 Mimante/FIX_DEFINITIVO_PORTA_v1.1.2.md delete mode 100644 Mimante/FIX_ERRORE_NAVIGATION_E_EMOJI.md delete mode 100644 Mimante/FIX_ERRORE_SECTION_REGISTRY.md delete mode 100644 Mimante/FIX_HEADERS_READ_ONLY_LOGIN.md delete mode 100644 Mimante/FIX_LAYOUT_LOGIN_PULITO.md delete mode 100644 Mimante/FIX_LOGIN_NON_APPARE.md delete mode 100644 Mimante/FIX_NAVIGATION_EXCEPTION_DEFINITIVO.md delete mode 100644 Mimante/FIX_PORTA_CONTAINER.md delete mode 100644 Mimante/NUOVO_WORKFLOW_RIEPILOGO.md delete mode 100644 Mimante/PROBLEMA_HTTPS_RISOLTO.md delete mode 100644 Mimante/PROBLEMA_RISOLTO.md delete mode 100644 Mimante/QUICKSTART_SECURITY.md delete mode 100644 Mimante/RELEASE_v1.1.1.md delete mode 100644 Mimante/RIEPILOGO_COMPLETO_FINALE.md delete mode 100644 Mimante/RIEPILOGO_RELEASE_v1.1.0.md delete mode 100644 Mimante/RIEPILOGO_SICUREZZA_v1.2.0.md delete mode 100644 Mimante/RIMOZIONE_CREDENZIALI_BIDOO.md delete mode 100644 Mimante/SECURITY.md create mode 100644 Mimante/Services/BidStrategyService.cs delete mode 100644 Mimante/UNRAID_TEMPLATE.md delete mode 100644 Mimante/VERIFICA_CONFIGURAZIONE_GITEA.md delete mode 100644 Mimante/VERSIONING.md delete mode 100644 Mimante/VERSIONING_IMPLEMENTATO.md diff --git a/Mimante/AutoBidder.csproj b/Mimante/AutoBidder.csproj index 0eaef9b..4ac6c0c 100644 --- a/Mimante/AutoBidder.csproj +++ b/Mimante/AutoBidder.csproj @@ -11,11 +11,11 @@ Dockerfile - - 1.2.0 - 1.2.0.0 - 1.2.0.0 - 1.2.0 + + 1.3.0 + 1.3.0.0 + 1.3.0.0 + 1.3.0 autobidder diff --git a/Mimante/CHANGELOG.md b/Mimante/CHANGELOG.md deleted file mode 100644 index 6451713..0000000 --- a/Mimante/CHANGELOG.md +++ /dev/null @@ -1,767 +0,0 @@ -# Changelog - -Tutte le modifiche rilevanti a questo progetto saranno documentate in questo file. - -Il formato è basato su [Keep a Changelog](https://keepachangelog.com/it/1.0.0/), -e questo progetto aderisce al [Semantic Versioning](https://semver.org/lang/it/). - - - - ---- - -## [1.2.0] - 2025-01-18 - -### ?? Aggiunte (Added) - SICUREZZA - -- **Sistema di autenticazione completo ASP.NET Core Identity** - - Login con username e password - - Protezione brute-force con lockout automatico (5 tentativi, 15 min block) - - Gestione sessioni sicura con cookie HttpOnly e SameSite - - Password policy forte (min 12 caratteri, maiuscole, minuscole, numeri, simboli) - -- **Protezione route con autorizzazione** - - Tutte le pagine richiedono autenticazione - - Redirect automatico a `/login` per utenti non autenticati - - Pagina logout dedicata - -- **Database Identity separato** - - SQLite per utenti e autenticazione - - Persistente su volume Docker `/app/Data` - - Inizializzazione automatica al primo avvio - -- **Utente amministratore predefinito** - - Username configurabile via `ADMIN_USERNAME` (default: `admin`) - - Password obbligatoria via `ADMIN_PASSWORD` in production - - Password temporanea forte se non configurata: `Admin@Password123!` - - Warning nei log se usa password default - -### ??? Modifiche (Changed) - SICUREZZA - -- **Cookie di autenticazione sicuri** - - `HttpOnly=true` (protezione XSS) - - `SameSite=Lax` (protezione CSRF) - - `SecurePolicy=SameAsRequest` (compatibile Tailscale HTTP) - - Durata 7 giorni con sliding expiration - -- **Configurazione Identity hardened** - - Lockout abilitato per nuovi utenti - - Timeout lockout: 15 minuti - - Max failed attempts: 5 - - Password unique chars: 4 - -- **UI aggiornata con logout** - - Indicatore utente corrente in NavMenu - - Pulsante logout in sidebar - - Pagina login styled con gradiente - -### ?? Note Tecniche - -**Configurazione richiesta in `.env`:** -```bash -# Credenziali amministratore (OBBLIGATORIO!) -ADMIN_USERNAME=admin -ADMIN_PASSWORD=TuaPasswordSicura123! -``` - -**Password temporanea default:** -- Se `ADMIN_PASSWORD` non è settata, usa: `Admin@Password123!` -- ?? **CAMBIARE IMMEDIATAMENTE** dopo primo login! -- Viene mostrato warning nei log se usa password default - -**Database:** -- Identity DB: `/app/Data/identity.db` (SQLite) -- Tabelle create automaticamente al primo avvio -- Utente admin creato se non esiste - -**Sicurezza Tailscale:** -- Cookie `SecurePolicy=SameAsRequest` (funziona su HTTP Tailscale) -- Rate limiting brute-force integrato -- Session management ASP.NET Core - -### ?? Breaking Changes - -**PRIMA INSTALLAZIONE v1.2.0:** -1. Aggiungere `ADMIN_PASSWORD` al file `.env` -2. Riavviare container -3. Primo accesso con username/password configurati -4. (Opzionale) Cambiare password default se usata - -**Aggiornamento da v1.1.x:** -- Primo avvio dopo aggiornamento creerà database Identity -- Se `ADMIN_PASSWORD` non settata, usa password temporanea -- ?? Cambiare password temporanea immediatamente! - -### ?? Raccomandazioni Sicurezza - -1. **Password forte obbligatoria:** - - Min 12 caratteri - - Maiuscole + minuscole - - Numeri - - Simboli speciali - - Esempio: `MyS3cur3P@ssw0rd!2024` - -2. **Backup database Identity:** - ```bash - docker cp AutoBidder:/app/Data/identity.db ./backup/ - ``` - -3. **Rotazione password periodica** -4. **Monitoraggio log accessi:** - ```bash - docker logs AutoBidder | grep "\[Identity\]" - ``` - ---- - -## [1.1.2] - 2025-01-18 - -### ?? Correzioni (Fixed) - -- **Fix critico: Container ascolta su porta 5000 invece di 8080** - - Forzato `UseUrls()` esplicito per garantire porta corretta - - Container ora ascolta definitivamente su porta 8080 - - Healthcheck ora passa correttamente - - Applicazione web accessibile correttamente - -### ?? Modifiche (Changed) - -- **Program.cs: Forzata porta con `UseUrls()`** - - Aggiunto controllo esplicito ASPNETCORE_URLS all'avvio - - Garantisce che nessuna configurazione sovrascriva la porta - - Log più chiaro della porta in ascolto - -- **Dockerfile: Healthcheck migliorato** - - Timeout aumentato a 30s (da 10s) - - Start period aumentato a 90s (da 40s) - - Retries aumentati a 5 (da 3) - - Più tempo per Blazor Server per avviarsi completamente - -### ?? Note Tecniche - -**Problema:** -- Container continuava ad ascoltare su porta 5000 invece di 8080 -- Healthcheck falliva: `curl: (7) Failed to connect to localhost port 8080` -- Log mostrava: `Now listening on: http://[::]:5000` - -**Root Cause:** -- Configurazioni di default .NET sovra scrivevano `ASPNETCORE_URLS` -- `launchSettings.json` poteva influenzare il comportamento - -**Soluzione:** -- Forzato `builder.WebHost.UseUrls()` esplicitamente nel Program.cs -- Garantisce precedenza assoluta sulla porta configurata -- Healthcheck aggiornato per Blazor Server (tempi più lunghi) - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.1.1] - 2025-01-18 - -### ?? Correzioni (Fixed) - -- **Fix critico: Container in ascolto su porta sbagliata** - - Container ora ascolta correttamente sulla porta 8080 (configurata in ASPNETCORE_URLS) - - Rimossa configurazione esplicita HTTP in Program.cs che causava conflitti - - Kestrel ora rispetta ASPNETCORE_URLS per la porta HTTP - - Pagina web ora carica correttamente quando si accede al container - -### ?? Modifiche (Changed) - -- **Configurazione Kestrel semplificata** - - HTTP gestito esclusivamente da ASPNETCORE_URLS - - Configurazione Kestrel utilizzata solo per HTTPS opzionale - - Log migliorato per mostrare porta di ascolto - -### ?? Note Tecniche - -**Problema:** Container ascoltava su porta 5000 invece di 8080, causando pagina che non caricava. - -**Causa:** Conflitto tra configurazione esplicita `options.ListenAnyIP(8080)` e impostazioni default Kestrel. - -**Soluzione:** Rimossa configurazione esplicita HTTP, ASPNETCORE_URLS ora gestisce tutto. - - -## [1.1.2] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.1.0] - 2025-01-18 - -### ? Aggiunte (Added) - -- **Pubblicazione automatica su Gitea Container Registry** - - Workflow integrato con Visual Studio (tasto destro ? Pubblica) - - Versionamento automatico da `` del `.csproj` - - Tag multipli: `latest` + versione specifica (es. `1.1.0`) - - Post-build target per push automatico su Gitea - -- **Profilo di pubblicazione `GiteaRegistry.pubxml`** - - Profilo custom senza dipendenze Docker SDK - - Target `DockerBuild` integrato - - Build e push automatici in un solo comando - -- **Documentazione completa Docker/Gitea** - - `DOCKER_PUBLISH_GUIDE.md`: Guida pubblicazione passo-passo - - `CONFIGURAZIONE_FINALE.md`: Riepilogo configurazione - - `PROBLEMA_RISOLTO.md`: Troubleshooting Visual Studio - - `PROBLEMA_HTTPS_RISOLTO.md`: Fix container HTTPS - - `RIEPILOGO_COMPLETO_FINALE.md`: Overview completa - -### ?? Modifiche (Changed) - -- **Porta HTTP container: `5000` ? `8080`** - - Porta standard per container HTTP - - Compatibile con convenzioni Docker/Kubernetes - -- **HTTPS disabilitato di default in container** - - `Kestrel__EnableHttps=false` nel Dockerfile - - HTTPS gestito da reverse proxy in production - - Certificati opzionali per chi ne ha bisogno - -- **Convenzione path Gitea Registry corretta** - - Da: `gitea.encke-hake.ts.net/alby96/mimante/autobidder` (4 livelli - errato) - - A: `gitea.encke-hake.ts.net/alby96/autobidder` (3 livelli - corretto) - - Conforme a standard Gitea `{registry}/{owner}/{image}` - -### ?? Correzioni (Fixed) - -- **Errore Visual Studio "ContainerBuild target not found"** - - Profilo cambiato da `WebPublishMethod=Docker` a `Custom` - - Rimossa dipendenza da Microsoft.Docker.Sdk non installato - - Visual Studio ora mostra SUCCESS senza errori - -- **Crash container all'avvio per certificati HTTPS** - - Kestrel non cerca più certificati di sviluppo inesistenti - - Container si avvia correttamente in modalità HTTP-only - - HTTPS abilitabile manualmente con certificato fornito - -- **Push Gitea falliva silenziosamente** - - Workflow ora completamente automatico e tracciabile - - Output dettagliato con conferma digest SHA256 - - Link diretto al package pubblicato - -### ??? Rimossi (Removed) - -- Profilo `GiteaRegistry-LocalOnly.pubxml` (ridondante) -- Dipendenza implicita da certificati HTTPS in Development - -### ?? Sicurezza (Security) - -- Gestione corretta certificati SSL/TLS -- HTTPS opzionale invece che obbligatorio -- Reverse proxy consigliato per terminazione SSL - -### ?? Note di Migrazione - -**Breaking Changes:** - -1. **Porta HTTP cambiata** - - Se usavi `5000:5000`, ora usa `5000:8080` - - Docker Compose: aggiornare port mapping - - Unraid: modificare configurazione porta container - -2. **HTTPS disabilitato** - - Se usavi HTTPS diretto, configura reverse proxy - - Oppure abilita manualmente con certificato: - ```bash - -e Kestrel__EnableHttps=true - -e Kestrel__Certificates__Default__Path=/certs/cert.pfx - ``` - -3. **Path Gitea cambiato** - - Le vecchie immagini `alby96/mimante/autobidder` rimangono disponibili - - Nuove immagini: `alby96/autobidder` - - Aggiornare pull command nei deployment - -**Aggiornamento consigliato:** - -```bash -# Pull nuova versione -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 - -# Stop vecchio container -docker stop autobidder -docker rm autobidder - -# Avvia nuovo container con porta corretta -docker run -d \ - --name autobidder \ - -p 5000:8080 \ - -v /data:/app/Data \ - gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 -``` - - -## [1.1.1] - 2026-01-20 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.1.2] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.1.2] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.0.0] - 2025-01-17 - -### ? Aggiunte (Added) - -- Release iniziale sistema AutoBidder -- Interfaccia Blazor Server .NET 8 -- Monitoraggio aste Bidoo in tempo reale -- Sistema di offerte automatiche -- Statistiche avanzate con PostgreSQL -- Backup database automatici -- Docker support di base - -### ?? Modifiche (Changed) - -- N/A (prima release) - -### ?? Correzioni (Fixed) - -- N/A (prima release) - - -## [1.1.1] - 2026-01-20 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.1.2] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.1.2] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## [1.2.0] - 2026-01-21 - -### ? Aggiunte (Added) - -- - -### ?? Modifiche (Changed) - -- - -### ?? Correzioni (Fixed) - -- - -### ??? Rimossi (Removed) - -- - -### ?? Breaking Changes - -- - ---- ---- - -## Tipologie di Modifiche - -- `? Aggiunte (Added)`: Nuove funzionalità -- `?? Modifiche (Changed)`: Modifiche a funzionalità esistenti -- `??? Rimossi (Removed)`: Funzionalità rimosse -- `?? Correzioni (Fixed)`: Bug fix -- `?? Sicurezza (Security)`: Fix di sicurezza -- `?? Deprecati (Deprecated)`: Funzionalità obsolete (da rimuovere) - -## Versioning - -Questo progetto segue [Semantic Versioning](https://semver.org/lang/it/): - -- **MAJOR** (1.x.x ? 2.x.x): Breaking changes incompatibili -- **MINOR** (x.1.x ? x.2.x): Nuove feature retrocompatibili -- **PATCH** (x.x.1 ? x.x.2): Bug fix retrocompatibili - -Esempi: -- `1.0.0` ? `1.1.0`: Nuova feature (Gitea publishing) -- `1.1.0` ? `1.1.1`: Bug fix -- `1.1.0` ? `2.0.0`: Breaking change (API cambiate) diff --git a/Mimante/CONFIGURAZIONE_FINALE.md b/Mimante/CONFIGURAZIONE_FINALE.md deleted file mode 100644 index 8c6c612..0000000 --- a/Mimante/CONFIGURAZIONE_FINALE.md +++ /dev/null @@ -1,296 +0,0 @@ -# ?? CONFIGURAZIONE FINALE - UN SOLO PROFILO - -## ? Cosa è Cambiato - -### PRIMA (Configurazione Complessa) -- ? Due profili: `GiteaRegistry` e `GiteaRegistry-LocalOnly` -- ? Versionamento manuale -- ? Confusione su quale profilo usare - -### DOPO (Configurazione Semplificata) -- ? **UN SOLO PROFILO**: `GiteaRegistry.pubxml` -- ? **Versionamento automatico** da `` della solution -- ? **Workflow chiaro** e lineare - ---- - -## ?? Struttura Files - -``` -AutoBidder/ -??? AutoBidder.csproj -? ??? 1.0.0 ? VERSIONE SOLUTION (fonte unica) -? ??? ? Post-build automatico -??? Dockerfile ? Build immagine Docker -??? Properties/ -? ??? PublishProfiles/ -? ??? GiteaRegistry.pubxml ? UNICO PROFILO (tutto automatico) -??? DOCKER_PUBLISH_GUIDE.md ? Guida aggiornata -``` - ---- - -## ?? Come Funziona - -### 1. Definisci Versione Solution - -```xml - - - 1.0.1 ? Modifica qui per nuova versione - -``` - -### 2. Pubblica da Visual Studio - -``` -Tasto destro progetto ? Pubblica ? GiteaRegistry ? Pubblica -``` - -### 3. Sistema Automatico - -``` -??????????????????????????????????? -? Visual Studio: Publish ? -??????????????????????????????????? - ? - ? -??????????????????????????????????? -? Build .NET (Release) ? -??????????????????????????????????? - ? - ? -??????????????????????????????????? -? Docker build ? -? ? autobidder:latest ? -??????????????????????????????????? - ? - ? -??????????????????????????????????? -? POST-BUILD (AutoBidder.csproj) ? -? ? -? Legge: 1.0.1 ? -? ? -? Tag: ? -? • autobidder:latest ? -? ? gitea.../alby96/ ? -? autobidder:latest ? -? ? -? • autobidder:latest ? -? ? gitea.../alby96/ ? -? autobidder:1.0.1 ? -??????????????????????????????????? - ? - ? -??????????????????????????????????? -? Push su Gitea ? -? ? -? ? latest (aggiornato) ? -? ? 1.0.1 (nuovo tag) ? -??????????????????????????????????? -``` - ---- - -## ?? Versionamento Automatico - -### Source of Truth - -```xml - -1.0.1 -``` - -### Tag Generati Automaticamente - -| Versione Solution | Tag Latest | Tag Versione | Nota | -|-------------------|------------|--------------|------| -| `1.0.0` | `:latest` ? 1.0.0 | `:1.0.0` | Prima versione | -| `1.0.1` | `:latest` ? 1.0.1 | `:1.0.1` + `:1.0.0` rimane | Latest aggiornato | -| `2.0.0` | `:latest` ? 2.0.0 | `:2.0.0` + precedenti | Major update | - -### Storico Versioni su Gitea - -Gitea mantiene **TUTTI i tag** pubblicati: - -``` -?? gitea.encke-hake.ts.net/alby96/autobidder -??? ??? latest (? 1.0.1) [sempre aggiornato] -??? ??? 1.0.1 [immutabile] -??? ??? 1.0.0 [immutabile] -??? ??? 0.9.0 [immutabile] -``` - ---- - -## ?? Esempio Pratico: Rilascio Versione 1.0.2 - -### Step 1: Aggiorna Versione - -```xml - -1.0.2 -``` - -### Step 2: Pubblica - -1. Tasto destro ? Pubblica -2. Seleziona `GiteaRegistry` -3. Click **Pubblica** - -### Step 3: Output Automatico - -``` -????????????????????????????????????????????????????????????????????? -? POST-BUILD: Pubblicazione su Gitea Container Registry ? -????????????????????????????????????????????????????????????????????? - -?? Solution Version: 1.0.2 -??? Target Tags: - • gitea.encke-hake.ts.net/alby96/autobidder:latest - • gitea.encke-hake.ts.net/alby96/autobidder:1.0.2 - -? Tagged: gitea.../autobidder:latest -? Tagged: gitea.../autobidder:1.0.2 - -? Pushed: gitea.../autobidder:latest -? Pushed: gitea.../autobidder:1.0.2 - -????????????????????????????????????????????????????????????????????? -? ? PUBBLICAZIONE COMPLETATA CON SUCCESSO! ? -????????????????????????????????????????????????????????????????????? - -?? Tag pubblicati: - • latest (ora punta a 1.0.2) - • 1.0.2 (nuova versione) -``` - -### Step 4: Verifica su Gitea - -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder -``` - -Vedrai: -- `latest` ? digest aggiornato (ora è 1.0.2) -- `1.0.2` ? nuovo tag creato -- `1.0.1` ? ancora disponibile -- `1.0.0` ? ancora disponibile - ---- - -## ?? Vantaggi del Nuovo Sistema - -| Aspetto | Prima | Dopo | -|---------|-------|------| -| **Profili** | 2 (confusione) | 1 (chiaro) | -| **Versionamento** | Manuale | Automatico | -| **Source of Truth** | Multipli | Unico (``) | -| **Complessità** | Alta | Bassa | -| **Errori** | Facili | Difficili | -| **Manutenibilità** | Difficile | Facile | - ---- - -## ?? Best Practices - -### 1. Semantic Versioning - -Segui il formato: `MAJOR.MINOR.PATCH` - -```xml - -1.0.0 ? Release iniziale -1.0.1 ? Bug fix -1.1.0 ? Nuova feature -2.0.0 ? Breaking change -``` - -### 2. Deploy Production - -**? MAI usare `latest` in production:** -```yaml -# ERRATO -image: gitea.../autobidder:latest -``` - -**? USA sempre versione specifica:** -```yaml -# CORRETTO -image: gitea.../autobidder:1.0.2 -``` - -### 3. Testing - -Prima di deployare in production: - -```bash -# 1. Pull versione specifica -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.0.2 - -# 2. Test locale -docker run -p 5000:8080 gitea.../autobidder:1.0.2 - -# 3. Verifica funzionalità -# http://localhost:5000 - -# 4. Se OK ? Deploy production -``` - -### 4. Changelog - -Mantieni un file `CHANGELOG.md` nella repo: - -```markdown -# Changelog - -## [1.0.2] - 2026-01-18 -### Fixed -- Correzione bug autenticazione Gitea - -## [1.0.1] - 2026-01-17 -### Added -- Supporto versionamento automatico -``` - ---- - -## ?? Comandi Rapidi - -```bash -# Autenticazione (prima volta) -docker login gitea.encke-hake.ts.net - -# Pubblica da Visual Studio -# Tasto destro ? Pubblica ? GiteaRegistry - -# Pull versione specifica (production) -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.0.2 - -# Pull latest (development) -docker pull gitea.encke-hake.ts.net/alby96/autobidder:latest - -# Lista tutti i tag disponibili (via API) -curl https://gitea.encke-hake.ts.net/api/v1/packages/Alby96/container/autobidder -``` - ---- - -## ?? File Finali - -| File | Scopo | -|------|-------| -| `AutoBidder.csproj` | Versione solution + post-build target | -| `Properties/PublishProfiles/GiteaRegistry.pubxml` | UNICO profilo pubblicazione | -| `Dockerfile` | Build immagine Docker | -| `.dockerignore` | Esclusioni Docker | -| `DOCKER_PUBLISH_GUIDE.md` | Guida utente completa | -| `VERIFICA_CONFIGURAZIONE_GITEA.md` | Checklist conformità | -| `NUOVO_WORKFLOW_RIEPILOGO.md` | Dettagli tecnici workflow | -| **`CONFIGURAZIONE_FINALE.md`** | **Questo documento** | - ---- - -**? CONFIGURAZIONE COMPLETATA E SEMPLIFICATA!** - -Ora hai un sistema **professionale**, **automatico** e **tracciabile** per gestire versioni Docker su Gitea! ?? diff --git a/Mimante/DOCKER_DEPLOY.md b/Mimante/DOCKER_DEPLOY.md deleted file mode 100644 index 721b8f9..0000000 --- a/Mimante/DOCKER_DEPLOY.md +++ /dev/null @@ -1,104 +0,0 @@ -# ?? AutoBidder - Docker Deploy su Gitea - -Setup minimalista per build e deploy Docker. - ---- - -## ?? Requisiti - -- Docker Desktop running -- Login Gitea Registry: - ```powershell - docker login gitea.encke-hake.ts.net - # Username: alby96 - # Password: - ``` - -**Genera token**: https://gitea.encke-hake.ts.net/user/settings/applications ? Permissions: `write:packages` - ---- - -## ?? Publish da Visual Studio - -``` -Build ? Publish ? Docker ? Publish -``` - -**Automatico**: -- Build immagine Docker -- Tag: `latest`, `1.0.0`, `1.0.0-20260118` -- Push su Gitea Registry - -**Registry**: https://gitea.encke-hake.ts.net/alby96/mimante/-/packages/container/autobidder - ---- - -## ?? Aggiornare Versione - -Modifica `AutoBidder.csproj`: -```xml - - 1.0.1 - -``` - -Poi publish come sopra. - ---- - -## ?? Deploy Unraid - -### Via Template - -1. Unraid ? Docker ? Add Template -2. URL: `https://192.168.30.23/Alby96/Mimante/raw/branch/docker/deployment/unraid-template.xml` -3. Install "AutoBidder" -4. Configura: - - Port: `8888:8080` - - AppData: `/mnt/user/appdata/autobidder` - - PostgreSQL: `Host=192.168.30.23;Port=5432;...` -5. Apply - -### Via Docker Compose - -```bash -docker-compose up -d -``` - -Accesso: http://localhost:8080 - ---- - -## ?? Troubleshooting - -### Publish fallisce: "unauthorized" - -```powershell -docker login gitea.encke-hake.ts.net -# Retry publish -``` - -### Container non parte - -```powershell -# Verifica porta libera -netstat -ano | findstr :8080 - -# Rebuild -docker build -t test . -``` - ---- - -## ?? File Configurazione - -| File | Scopo | -|------|-------| -| `Dockerfile` | Build immagine multi-stage | -| `docker-compose.yml` | Deploy con PostgreSQL | -| `Properties/PublishProfiles/Docker.pubxml` | Profilo publish Visual Studio | -| `deployment/unraid-template.xml` | Template Unraid | - ---- - -**Setup completo! Build ? Publish ? Docker per deployare! ??** diff --git a/Mimante/DOCKER_PUBLISH_GUIDE.md b/Mimante/DOCKER_PUBLISH_GUIDE.md deleted file mode 100644 index e408dbe..0000000 --- a/Mimante/DOCKER_PUBLISH_GUIDE.md +++ /dev/null @@ -1,505 +0,0 @@ -# Guida Pubblicazione Docker su Gitea Registry - -Questa guida spiega come pubblicare l'immagine Docker di AutoBidder sul registry Gitea usando il **nuovo workflow integrato con Visual Studio**. - -## Prerequisiti - -1. **Docker installato e in esecuzione** -2. **Accesso al registry Gitea**: `gitea.encke-hake.ts.net` -3. **Token PAT** (Personal Access Token) con permessi `read:packages` e `write:packages` - -## 1. Autenticazione con Gitea (OBBLIGATORIA) - -Prima di pubblicare, devi autenticarti con il registry Gitea usando un **Token PAT**: - -### Genera Token PAT - -1. Vai su: `https://gitea.encke-hake.ts.net/user/settings/applications` -2. Click **Generate New Token** -3. Seleziona scope: **`read:packages`** + **`write:packages`** -4. Copia il token generato - -### Autentica Docker - -```bash -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [INCOLLA IL TOKEN PAT QUI] -``` - -**IMPORTANTE:** Se hai 2FA attivo su Gitea, il Token PAT è **OBBLIGATORIO** (la password normale non funziona). - ---- - -# Guida Pubblicazione Docker su Gitea Registry - -Questa guida spiega come pubblicare l'immagine Docker di AutoBidder sul registry Gitea con **versionamento automatico** basato sulla solution. - -## Prerequisiti - -1. **Docker Desktop** installato e in esecuzione -2. **Accesso al registry Gitea**: `gitea.encke-hake.ts.net` -3. **Token PAT** (Personal Access Token) con permessi `read:packages` e `write:packages` - ---- - -## 1. Autenticazione con Gitea (OBBLIGATORIA - Una Volta) - -### Genera Token PAT - -1. Vai su: `https://gitea.encke-hake.ts.net/user/settings/applications` -2. Click **Generate New Token** -3. Nome: `Docker Registry Access` -4. Seleziona scope: **`read:packages`** + **`write:packages`** -5. Click **Generate Token** -6. **Copia il token** (non sarà più visibile!) - -### Autentica Docker - -```bash -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [INCOLLA IL TOKEN PAT] -``` - -**? Success:** `Login Succeeded` - -**?? IMPORTANTE:** Con 2FA attivo su Gitea, il Token PAT è **OBBLIGATORIO** (la password normale non funziona). - ---- - -## 2. Pubblicare su Gitea con Versionamento Automatico - -### ?? Workflow Completo in 3 Step - -#### Step 1: Aggiorna Versione Solution (Opzionale) - -Apri `AutoBidder.csproj` e modifica: - -```xml -1.0.1 -``` - -La versione qui definita sarà usata per taggare l'immagine Docker. - -#### Step 2: Pubblica da Visual Studio - -1. **Tasto destro** sul progetto `AutoBidder` -2. Seleziona **Pubblica** -3. Scegli il profilo: **`GiteaRegistry`** (UNICO profilo disponibile) -4. Click **Pubblica** - -#### Step 3: Verifica Pubblicazione - -Il sistema mostrerà output dettagliato: - -``` -????????????????????????????????????????????????????????????????????? -? POST-BUILD: Pubblicazione su Gitea Container Registry ? -????????????????????????????????????????????????????????????????????? - -?? Solution Version: 1.0.1 -?? Local Image: autobidder:latest -??? Target Tags: - • gitea.encke-hake.ts.net/alby96/autobidder:latest - • gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 - -??????????????????????????????????????????????????????????????????? -??? Tagging images... -??????????????????????????????????????????????????????????????????? -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 - -??????????????????????????????????????????????????????????????????? -?? Pushing to Gitea Registry... -??????????????????????????????????????????????????????????????????? -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 - -????????????????????????????????????????????????????????????????????? -? ? PUBBLICAZIONE COMPLETATA CON SUCCESSO! ? -????????????????????????????????????????????????????????????????????? -``` - ---- - -## 3. Sistema di Versionamento - -### Come Funziona - -Il versionamento è **completamente automatico** e basato su: - -```xml - -1.0.1 -``` - -Quando pubblichi: -- ? Tag `latest` ? **sempre aggiornato** all'ultima versione -- ? Tag `1.0.1` ? **versione specifica** immutabile - -### Esempi Pratici - -**Scenario 1: Prima pubblicazione** -```xml -1.0.0 -``` -Risultato: -- `gitea.../alby96/autobidder:latest` ? v1.0.0 -- `gitea.../alby96/autobidder:1.0.0` ? v1.0.0 - -**Scenario 2: Aggiornamento versione** -```xml -1.0.1 -``` -Risultato: -- `gitea.../alby96/autobidder:latest` ? **aggiornato** a v1.0.1 -- `gitea.../alby96/autobidder:1.0.1` ? **nuovo tag** creato -- `gitea.../alby96/autobidder:1.0.0` ? rimane disponibile - -### Best Practices - -| Ambiente | Tag Consigliato | Motivo | -|----------|----------------|---------| -| **Development** | `latest` | Sempre l'ultima versione | -| **Staging** | `1.0.1` | Versione specifica per test | -| **Production** | `1.0.1` | Versione immutabile e tracciabile | - ---- - -## 4. Dove Trovare le Immagini Pubblicate - -### Link Diretto al Package -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder -``` - -### Lista Packages Utente -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages -``` - -Su Gitea vedrai: -- ?? Nome: **`autobidder`** -- ??? Tag: `latest`, `1.0.0`, `1.0.1`, ... -- ?? Data pubblicazione -- ?? Digest SHA256 -- ?? Dimensione immagine - ---- - -## 5. Usare l'Immagine Pubblicata - -### Pull con Versione Specifica - -```bash -# Versione immutabile (CONSIGLIATO per production) -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 - -# Latest (sempre aggiornato) -docker pull gitea.encke-hake.ts.net/alby96/autobidder:latest -``` - -### Su Unraid - -1. Docker tab ? **Add Container** -2. **Repository**: `gitea.encke-hake.ts.net/alby96/autobidder:1.0.1` -3. **Port**: `5000` ? `8080` -4. **Volume 1**: `/mnt/user/appdata/autobidder/data` ? `/app/Data` -5. **Volume 2**: `/mnt/user/appdata/autobidder/logs` ? `/app/logs` -6. **Environment**: `ASPNETCORE_ENVIRONMENT=Production` -7. **Restart**: `unless-stopped` - -### Docker Compose - -```yaml -version: '3.8' - -services: - autobidder: - image: gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 # Versione specifica - container_name: autobidder - ports: - - "5000:8080" - volumes: - - ./data:/app/Data - - ./logs:/app/logs - environment: - - ASPNETCORE_ENVIRONMENT=Production - restart: unless-stopped -``` - -### Docker Run - -```bash -docker run -d \ - --name autobidder \ - -p 5000:8080 \ - -v /path/to/data:/app/Data \ - -v /path/to/logs:/app/logs \ - -e ASPNETCORE_ENVIRONMENT=Production \ - --restart unless-stopped \ - gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 -``` - ---- - -## 6. Troubleshooting - -### Errore: "unauthorized: authentication required" - -```bash -# Re-autentica con Token PAT -docker logout gitea.encke-hake.ts.net -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [TOKEN PAT] -``` - -### Errore: "denied: requested access to the resource is denied" - -**Causa:** Token PAT senza permessi corretti o scaduto - -**Soluzione:** -1. Vai su: `https://gitea.encke-hake.ts.net/user/settings/applications` -2. Verifica che il token abbia: `read:packages` + `write:packages` -3. Se scaduto, genera nuovo token - -### Container non parte: errore certificato HTTPS - -**Sintomo:** -``` -System.InvalidOperationException: Unable to configure HTTPS endpoint. -No server certificate was specified, and the default developer certificate -could not be found or is out of date. -``` - -**Causa:** Kestrel cerca di abilitare HTTPS ma non trova certificati di sviluppo nel container. - -**? RISOLTO:** -- HTTPS disabilitato di default in container (`Kestrel__EnableHttps=false`) -- Porta HTTP: `8080` (standard container) -- SSL gestito dal reverse proxy (nginx/traefik) in production - -**Per abilitare HTTPS manualmente** (se hai un certificato): -```bash -docker run -d \ - -e Kestrel__EnableHttps=true \ - -e Kestrel__Certificates__Default__Path=/path/to/cert.pfx \ - -e Kestrel__Certificates__Default__Password=yourpassword \ - -v /path/to/certs:/certs \ - gitea.../autobidder:latest -``` - -### Errore: "La compilazione non è riuscita" ma il push è riuscito - -**Sintomo:** -Visual Studio mostra: -``` -Errore MSB4057: la destinazione "ContainerBuild" non è presente nel progetto -``` - -Ma nel log vedi: -``` -? Pushed: gitea.../autobidder:latest -? Pushed: gitea.../autobidder:1.0.0 -``` - -**Causa:** Il profilo stava usando `WebPublishMethod=Docker` che richiede Microsoft.Docker.Sdk non installato. - -**? RISOLTO:** Il profilo è stato corretto per usare `WebPublishMethod=Custom` che non richiede SDK aggiuntivi. - -### Verifica push su Gitea - -```bash -# Test manuale push -docker push gitea.encke-hake.ts.net/alby96/autobidder:latest - -# Se fallisce, verifica autenticazione -docker logout gitea.encke-hake.ts.net -docker login gitea.encke-hake.ts.net -``` - -### Versione non cambia su Gitea - -**Verifica:** -1. Hai modificato `` in `AutoBidder.csproj`? -2. Hai fatto Rebuild completo? -3. Visual Studio ha mostrato il nuovo numero versione nell'output? - -**Soluzione:** Rebuild completo -```bash -# Da Visual Studio: Build ? Rebuild Solution -# Poi: Tasto destro ? Pubblica ? GiteaRegistry -``` - ---- - -## 7. Riferimenti - -- **Registry URL**: `https://gitea.encke-hake.ts.net` -- **Repository Codice**: `https://gitea.encke-hake.ts.net/Alby96/Mimante` -- **Packages Container**: `https://gitea.encke-hake.ts.net/Alby96/-/packages` -- **Package Autobidder**: `https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder` -- **Convenzione Gitea**: `{registro}/{owner}/{image}:{tag}` (3 livelli) - ---- - -## 8. Riepilogo Comandi Rapidi - -```bash -# 1. Autenticazione (prima volta) -docker login gitea.encke-hake.ts.net - -# 2. Pubblica da Visual Studio -# Tasto destro ? Pubblica ? GiteaRegistry - -# 3. Pull versione specifica -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 - -# 4. Pull latest -docker pull gitea.encke-hake.ts.net/alby96/autobidder:latest - -# 5. Run container -docker run -d --name autobidder \ - -p 5000:8080 \ - -v /data:/app/Data \ - gitea.encke-hake.ts.net/alby96/autobidder:1.0.1 -``` - ---- - -**? CONFIGURAZIONE COMPLETATA!** - -Ora hai un sistema di pubblicazione Docker con **versionamento automatico** completamente integrato! ?? - -## 3. Dove Trovare il Package su Gitea - -**IL PACKAGE E' PUBBLICATO!** Cercalo in uno di questi percorsi: - -### Percorso 1: Packages del Tuo Profilo (PRINCIPALE) -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages -``` -Cerca un package di tipo **Container** con nome: `mimante/autobidder` oppure `mimante` - -### Percorso 2: Explore Packages -``` -https://gitea.encke-hake.ts.net/explore/packages -``` -Filtra per tipo "Container" e cerca `mimante` o `autobidder` - -### Percorso 3: Packages del Repository -``` -https://gitea.encke-hake.ts.net/Alby96/Mimante/-/packages -``` - -### Verifica Push Riuscito - -Se hai eseguito il push e vedi nell'output: -``` -latest: digest: sha256:cb7621ed1f22... size: 856 -``` -Significa che **il package E' STATO pubblicato correttamente!** - -Per verificare: -```bash -docker push gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest -``` - -## 4. Usare l'Immagine Pubblicata - -### Su Unraid - -1. Vai su **Docker** tab -2. Click **Add Container** -3. **Repository**: `gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest` -4. **Port**: `5000` -> `8080` (container) -5. **Volume**: `/mnt/user/appdata/autobidder/data` -> `/app/Data` -6. **Volume**: `/mnt/user/appdata/autobidder/logs` -> `/app/logs` -7. **Environment**: `ASPNETCORE_ENVIRONMENT=Production` - -### Docker Compose - -```yaml -version: '3.8' - -services: - autobidder: - image: gitea.encke-hake.ts.net/alby96/autobidder:latest - container_name: autobidder - ports: - - "5000:8080" - volumes: - - ./data:/app/Data - - ./logs:/app/logs - environment: - - ASPNETCORE_ENVIRONMENT=Production - restart: unless-stopped -``` - -### Docker Run - -```bash -docker pull gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest - -docker run -d --name autobidder -p 5000:8080 -v /path/to/data:/app/Data -v /path/to/logs:/app/logs -e ASPNETCORE_ENVIRONMENT=Production gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest -``` - -## 5. Aggiornare la Versione - -1. Apri `AutoBidder.csproj` -2. Modifica il tag ``: - ```xml - 1.0.1 - ``` -3. Pubblica: - ```bash - dotnet publish /p:PublishProfile=GiteaRegistry - ``` - -## Troubleshooting - -### Errore: "unauthorized: authentication required" - -```bash -docker login gitea.encke-hake.ts.net -``` - -### Package non visibile su Gitea - -**Il package c'e'!** Controlla in: -- `https://gitea.encke-hake.ts.net/Alby96/-/packages` (packages utente) -- `https://gitea.encke-hake.ts.net/explore/packages` (tutti) - -Cerca per nome: `mimante`, `autobidder`, o `mimante/autobidder` (tipo: Container) - -Se vedi `digest: sha256:...` nel push, il package E' pubblicato. - -## Riferimenti - -- **Registry**: `https://gitea.encke-hake.ts.net` -- **Repository**: `https://gitea.encke-hake.ts.net/Alby96/Mimante` -- **Packages**: `https://gitea.encke-hake.ts.net/Alby96/-/packages` ? -- **Package Diretto**: `https://gitea.encke-hake.ts.net/Alby96/-/packages/container/mimante%2Fautobidder/latest` -- **Immagine**: `gitea.encke-hake.ts.net/alby96/mimante/autobidder` - -## Comandi Rapidi - -```bash -# 1. Login -docker login gitea.encke-hake.ts.net - -# 2. Build e push -dotnet publish /p:PublishProfile=GiteaRegistry - -# 3. Pull -docker pull gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest - -# 4. Run -docker run -d --name autobidder -p 5000:8080 -v /data:/app/Data gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest -``` - ---- - -**Il package E' stato pubblicato!** Verifica su: `https://gitea.encke-hake.ts.net/Alby96/-/packages` diff --git a/Mimante/FIX_DEFINITIVO_PORTA_v1.1.2.md b/Mimante/FIX_DEFINITIVO_PORTA_v1.1.2.md deleted file mode 100644 index 858fa9d..0000000 --- a/Mimante/FIX_DEFINITIVO_PORTA_v1.1.2.md +++ /dev/null @@ -1,272 +0,0 @@ -# ? FIX DEFINITIVO v1.1.2 - Porta Container - -## ?? Problema Risolto - -**Container ascoltava su porta 5000 invece di 8080** - ---- - -## ? Sintomi - -``` -docker logs AutoBidder: - Now listening on: http://[::]:5000 ? - -Healthcheck: - curl: (7) Failed to connect to localhost port 8080 ? - -Port mapping: - 0.0.0.0:8889->8080/tcp ? -``` - -**Risultato:** Healthcheck unhealthy, applicazione non accessibile - ---- - -## ?? Root Cause - -Dopo analisi approfondita dei log: - -``` -warn: Microsoft.AspNetCore.Server.Kestrel[0] - Overriding address(es) 'http://+:8080'. - Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -``` - -**Problema:** Una configurazione di default .NET sovra scriveva `ASPNETCORE_URLS`. - -**Sospetti:** -- `launchSettings.json` con `applicationUrl: http://localhost:5000` -- Configurazioni Kestrel implicite -- Precedenza configurazione .NET vs env vars - ---- - -## ? Soluzione Applicata - -### 1. Forzato `UseUrls()` Esplicito - -**Program.cs:** -```csharp -var builder = WebApplication.CreateBuilder(args); - -// FORCE ASPNETCORE_URLS to prevent any override -if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ASPNETCORE_URLS"))) -{ - builder.WebHost.UseUrls("http://+:8080"); -} -else -{ - builder.WebHost.UseUrls(Environment.GetEnvironmentVariable("ASPNETCORE_URLS")!); -} -``` - -**Benefici:** -- ? Precedenza ASSOLUTA sulla porta -- ? Rispetta `ASPNETCORE_URLS` se definita -- ? Fallback sicuro a 8080 -- ? Nessuna configurazione può sovrascrivere - -### 2. Migliorato Healthcheck - -**Dockerfile:** -```docker -HEALTHCHECK --interval=30s --timeout=30s --start-period=90s --retries=5 \ - CMD curl -f http://localhost:8080/ || exit 1 -``` - -**Modifiche:** -- Timeout: 10s ? 30s -- Start period: 40s ? 90s -- Retries: 3 ? 5 - -**Motivo:** Blazor Server richiede più tempo per avviarsi completamente - ---- - -## ?? Come Aggiornare - -### Opzione 1: Pull Nuova Immagine da Gitea - -```bash -# Stop container vecchio -docker stop AutoBidder -docker rm AutoBidder - -# Pull v1.1.2 -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.1.2 - -# Avvia nuovo container -docker run -d \ - --name AutoBidder \ - -p 8889:8080 \ - -v /mnt/user/appdata/autobidder/data:/app/Data \ - gitea.encke-hake.ts.net/alby96/autobidder:1.1.2 -``` - -### Opzione 2: Build Locale - -```bash -# Build nuova immagine -docker build -t autobidder:1.1.2 . - -# Avvia container -docker run -d \ - --name AutoBidder \ - -p 8889:8080 \ - -v /mnt/user/appdata/autobidder/data:/app/Data \ - autobidder:1.1.2 -``` - -### Opzione 3: Unraid - -1. **Stop container** -2. **Edit template** -3. **Repository:** `gitea.encke-hake.ts.net/alby96/autobidder:1.1.2` -4. **Apply** -5. **Start container** - ---- - -## ? Verifica Fix - -### 1. Controlla Log - -```bash -docker logs AutoBidder | grep "listening" - -# Output ATTESO: -# [Kestrel] Listening on: http://+:8080 -# info: Now listening on: http://[::]:8080 ? -``` - -### 2. Verifica Healthcheck - -```bash -# Aspetta 90 secondi (start-period), poi: -docker inspect AutoBidder | grep -A 5 '"Status"' - -# Output ATTESO: -# "Status": "healthy", ? -``` - -### 3. Test Endpoint - -```bash -# Dall'interno container -docker exec AutoBidder curl -f http://localhost:8080/ -# Deve rispondere con HTML ? - -# Dal browser -http://192.168.30.23:8889 -# Homepage AutoBidder deve caricare ? -``` - ---- - -## ?? Confronto Versioni - -| Aspetto | v1.1.1 | v1.1.2 | -|---------|--------|--------| -| **Porta Ascolto** | ? 5000 | ? 8080 | -| **Healthcheck** | ? Unhealthy | ? Healthy | -| **Accessibilità** | ? Connection refused | ? Funzionante | -| **UseUrls() Forzato** | ? No | ? Sì | -| **Timeout Healthcheck** | 10s | 30s | -| **Start Period** | 40s | 90s | - ---- - -## ?? Lezioni Apprese - -### 1. ASPNETCORE_URLS Non Sempre Funziona - -**Problema:** Variabile env può essere sovrascritta da: -- `launchSettings.json` -- Configurazioni IConfiguration -- Default Kestrel - -**Soluzione:** Usare `UseUrls()` esplicito per precedenza assoluta - -### 2. Healthcheck Deve Considerare App Type - -**Blazor Server:** -- Richiede più tempo per avviarsi -- SignalR deve inizializzare -- Timeout default troppo brevi - -**Best Practice:** -- Start period: almeno 60-90s -- Timeout: 30s -- Retries: 5+ - -### 3. Verifica Sempre i Log - -**Comando essenziale:** -```bash -docker logs | grep "listening" -``` - -Mostra la porta EFFETTIVA, non quella configurata! - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| **Program.cs** | Aggiunto `UseUrls()` forzato | Garantire porta corretta | -| **Dockerfile** | Healthcheck timeout/retries aumentati | Blazor Server startup | -| **AutoBidder.csproj** | Versione `1.1.2` | Incremento PATCH | -| **CHANGELOG.md** | Entry v1.1.2 | Documentazione fix | - ---- - -## ?? Stato Finale - -``` -? Container ascolta su porta 8080 -? Healthcheck passa (healthy) -? Applicazione accessibile da browser -? Port mapping corretto (8889:8080) -? Log mostra porta corretta -? Fix testato e verificato -``` - ---- - -## ?? Prossimi Passi - -### 1. Pubblica su Gitea - -```bash -# Da Visual Studio -# Tasto destro ? Pubblica ? GiteaRegistry - -# Oppure CLI -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -### 2. Deploy su Unraid - -```bash -# Aggiorna repository a: -gitea.encke-hake.ts.net/alby96/autobidder:1.1.2 - -# Restart container -``` - -### 3. Verifica Finale - -```bash -# Browser -http://192.168.30.23:8889 - -# Dovrebbe mostrare homepage AutoBidder ? -``` - ---- - -**? v1.1.2 - FIX DEFINITIVO PORTA CONTAINER!** - -Ora il container funziona correttamente! ?? diff --git a/Mimante/FIX_ERRORE_NAVIGATION_E_EMOJI.md b/Mimante/FIX_ERRORE_NAVIGATION_E_EMOJI.md deleted file mode 100644 index 1eed75c..0000000 --- a/Mimante/FIX_ERRORE_NAVIGATION_E_EMOJI.md +++ /dev/null @@ -1,309 +0,0 @@ -# ? FIX APPLICATI - Errore NavigationException + Emoji Login - -## ?? Analisi Errore nei Log - -### Errore Rilevato - -``` -Eccezione generata: 'Microsoft.AspNetCore.Components.NavigationException' -in Microsoft.AspNetCore.Components.Server.dll -Eccezione di tipo 'Microsoft.AspNetCore.Components.NavigationException' -in Microsoft.AspNetCore.Components.Server.dll non gestita nel codice utente -``` - -### ? Spiegazione - -**Questo NON è un errore da correggere!** - -L'eccezione `NavigationException` è il comportamento **normale** e **previsto** quando si usa: - -```csharp -Navigation.NavigateTo("/login", forceLoad: true); -``` - -**Come funziona:** - -1. `forceLoad: true` forza un refresh completo della pagina -2. Blazor Server lancia internamente una `NavigationException` -3. Il framework la gestisce correttamente -4. Il redirect viene eseguito con successo -5. L'applicazione continua a funzionare normalmente - -**Evidenza dal log:** -``` -Microsoft.Hosting.Lifetime: Information: Now listening on: http://localhost:5000 -Microsoft.Hosting.Lifetime: Information: Application started. Press Ctrl+C to shut down. -``` - -? L'applicazione si è avviata correttamente -? Il redirect funziona -? Nessun crash o malfunzionamento - -### ?? Riferimento Microsoft - -Documentazione ufficiale: -> "NavigationException is thrown when NavigateTo is called with forceLoad: true. -> This is expected behavior and should not be caught or handled." - -[ASP.NET Core Blazor Routing - NavigationException](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/routing) - ---- - -## ?? FIX: Rimozione Emoji dalla Pagina Login - -### Problema - -Caratteri `??` visualizzati al posto di emoji nella pagina di login. - -**Causa:** Font Windows che non supportano emoji Unicode moderni. - -### Emoji Rimossi - -**Prima:** -```razor -

?? AutoBidder

-``` - -**Dopo:** -```razor -

AutoBidder

-``` - -### File Modificato - -- `Pages/Login.razor` - Rimosso emoji dal titolo - -**Risultato:** Titolo pulito e leggibile su tutti i sistemi Windows. - ---- - -## ?? Credenziali di Default - -### Configurazione Attuale - -**Username di default:** -```docker -# Dockerfile -ENV ADMIN_USERNAME=admin -``` - -**Password di default:** -```csharp -// Program.cs (già implementato) -var adminPassword = Environment.GetEnvironmentVariable("ADMIN_PASSWORD"); - -if (string.IsNullOrEmpty(adminPassword)) -{ - Console.WriteLine("[Identity] WARNING: ADMIN_PASSWORD not set! Using default password."); - Console.WriteLine("[Identity] CHANGE IT IMMEDIATELY after first login!"); - adminPassword = "Admin@Password123!"; // Password temporanea FORTE -} -``` - -### Credenziali Preimpostate - -| Campo | Valore Default | Configurabile | -|-------|---------------|---------------| -| **Username** | `admin` | ? Sì (via `ADMIN_USERNAME`) | -| **Password** | `Admin@Password123!` | ? Sì (via `ADMIN_PASSWORD`) | - -### Come Funziona - -``` -1. Container avviato - ? -2. Program.cs legge ADMIN_PASSWORD - ? -3. Se ADMIN_PASSWORD vuota: - - Usa password default: Admin@Password123! - - WARNING nei log ?? - ? -4. Se ADMIN_PASSWORD configurata: - - Usa quella password - - Nessun warning ? -``` - -### Primo Login - -**Con credenziali di default:** -``` -Username: admin -Password: Admin@Password123! -``` - -**?? Container mostrerà:** -``` -[Identity] WARNING: ADMIN_PASSWORD not set! Using default password. -[Identity] CHANGE IT IMMEDIATELY after first login! -[Identity] Admin user created: admin -[Identity] ?? REMEMBER TO CHANGE THE DEFAULT PASSWORD! -``` - -### Visualizzazione Credenziali nella Pagina Login - -**NUOVO**: Se `ADMIN_PASSWORD` non è configurata, la pagina di login mostra le credenziali di default: - -```razor -@if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ADMIN_PASSWORD"))) -{ -
-

Credenziali di default:

-

Username: admin

-

Password: Admin@Password123!

-

CAMBIARE IMMEDIATAMENTE!

-
-} -``` - -**Vantaggi:** -- ? Utente sa subito quali credenziali usare -- ? Warning visibile per cambio password -- ? Box appare SOLO se password non configurata -- ? Produzione con ADMIN_PASSWORD configurata: box NON appare - ---- - -## ?? Test Completo - -### Test 1: Avvio con Password di Default - -```bash -# NON configurare ADMIN_PASSWORD -docker run -d -p 8889:8080 autobidder:1.2.0 - -# Log attesi: -[Identity] WARNING: ADMIN_PASSWORD not set! Using default password. -[Identity] Admin user created: admin -[Identity] ?? REMEMBER TO CHANGE THE DEFAULT PASSWORD! - -# Pagina login: -- Titolo: "AutoBidder" (senza emoji ?) -- Box giallo con credenziali: VISIBILE ? -- Username: admin -- Password: Admin@Password123! -``` - -### Test 2: Avvio con Password Configurata - -```bash -docker run -d \ - -p 8889:8080 \ - -e ADMIN_PASSWORD="MyS3cur3P@ss!2024" \ - autobidder:1.2.0 - -# Log attesi: -[Identity] Admin user created: admin -(NESSUN warning) - -# Pagina login: -- Titolo: "AutoBidder" (senza emoji ?) -- Box giallo credenziali: NON VISIBILE ? -- Username: admin -- Password: MyS3cur3P@ss!2024 -``` - -### Test 3: Redirect Login Funziona - -``` -1. Browser: http://localhost:8889 -2. REDIRECT AUTOMATICO ? /login ? -3. Nessun errore visibile ? -4. Log: NavigationException (normale) ? -5. Pagina login carica ? -``` - ---- - -## ? Checklist Correzioni - -- [x] **Analizzato errore NavigationException** ? Comportamento normale ? -- [x] **Rimosso emoji da Login.razor** ? Titolo pulito ? -- [x] **Verificato credenziali di default** ? Già implementate ? -- [x] **Aggiunto box credenziali in pagina login** ? Per sviluppo/test ? -- [x] **Dockerfile con ADMIN_USERNAME=admin** ? Default corretto ? -- [x] **Program.cs con fallback password** ? Admin@Password123! ? - ---- - -## ?? Risultato Finale - -### Comportamento Corretto - -``` -Primo avvio (senza ADMIN_PASSWORD configurata): - -1. Container parte ? -2. Log WARNING password default ? -3. Utente admin creato con password temporanea ? -4. Browser ? redirect a /login ? -5. Pagina login mostra box giallo con credenziali ? -6. Login con admin / Admin@Password123! ? -7. Accesso homepage AutoBidder ? -``` - -### Sicurezza Mantenuta - -- ? Password default FORTE (12+ caratteri, simboli, numeri) -- ? Warning visibili nei log se usa password default -- ? Box credenziali appare SOLO in sviluppo (ADMIN_PASSWORD non configurata) -- ? Produzione con ADMIN_PASSWORD ? nessun warning, nessun box - -### User Experience Migliorata - -- ? Emoji rimossi ? titolo leggibile su tutti i sistemi -- ? Credenziali default visibili ? primo accesso facile -- ? Warning chiari ? sicurezza rafforzata -- ? Nessun errore visibile ? esperienza pulita - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| `Pages/Login.razor` | Rimosso emoji `??` | Fix caratteri ?? su Windows | -| `Pages/Login.razor` | Aggiunto box credenziali default | UX migliorata per sviluppo | - -**Nessuna modifica a:** -- `Program.cs` - Logica password default già presente ? -- `Dockerfile` - ADMIN_USERNAME già configurato ? - ---- - -## ?? Prossimi Passi - -### Per Sviluppatore - -1. ? Nessuna modifica necessaria -2. ? Funziona già correttamente -3. ? Testare login con credenziali default - -### Per Utente Finale - -1. **Primo deploy:** - ```bash - docker run -d -p 8889:8080 autobidder:1.2.0 - ``` - -2. **Login con credenziali default:** - - Username: `admin` - - Password: `Admin@Password123!` - -3. **Configurazione produzione:** - ```bash - docker run -d \ - -p 8889:8080 \ - -e ADMIN_PASSWORD="MiaPasswordSicura!2024" \ - autobidder:1.2.0 - ``` - ---- - -**? TUTTO RISOLTO!** - -- ? Errore NavigationException: comportamento normale -- ? Emoji rimossi: pagina login pulita -- ? Credenziali default: configurate e documentate -- ? Box informativo: visibile solo quando necessario - -**?? Pronto per il deploy!** diff --git a/Mimante/FIX_ERRORE_SECTION_REGISTRY.md b/Mimante/FIX_ERRORE_SECTION_REGISTRY.md deleted file mode 100644 index 85156ef..0000000 --- a/Mimante/FIX_ERRORE_SECTION_REGISTRY.md +++ /dev/null @@ -1,402 +0,0 @@ -# ? FIX: Errore SectionRegistry - Layout Duplicato Risolto - -## ?? Errore Identificato - -``` -System.InvalidOperationException: There is already a subscriber to the content -with the given section ID 'System.Object'. - at Microsoft.AspNetCore.Components.Sections.SectionRegistry.Subscribe -``` - -**Causa:** `LoginLayout.razor` conteneva un HTML completo con ``, creando un duplicato con quello già presente in `_Host.cshtml`. - ---- - -## ??? Architettura Blazor Server - -### Come Funziona il Rendering - -``` -_Host.cshtml (HTML esterno) - ? - - ? -App.razor (Router) - ? -Layout (MainLayout o LoginLayout) - ? -Page (Index, Login, etc.) -``` - -**Regola importante:** Solo `_Host.cshtml` deve contenere: -- `` -- ``, ``, `` -- `` - -I **Layout** (`.razor`) devono contenere SOLO: -- `@inherits LayoutComponentBase` -- `@Body` per il contenuto -- CSS/JS inline se necessario - ---- - -## ? Soluzione Applicata - -### Prima (ERRATO - causava duplicazione) - -```razor -@inherits LayoutComponentBase - - ? ? DUPLICATO (già in _Host.cshtml) - ? ? DUPLICATO - ? ? DUPLICATO - ? ? DUPLICATO (già in _Host.cshtml) - - ? ? DUPLICATO - @Body - - -``` - -**Problema:** `_Host.cshtml` ha già ``, creando quindi DUE outlet con lo stesso ID. - -### Dopo (CORRETTO - minimal layout) - -```razor -@inherits LayoutComponentBase - - - - -``` - -**Vantaggi:** -- ? Nessuna duplicazione HTML -- ? Nessun `` duplicato -- ? CSS inline per nascondere sidebar -- ? Fullscreen layout per login - ---- - -## ?? Come Funziona Ora - -### Rendering Pagina Login - -``` -1. Browser richiede: http://localhost:5000 - ? -2. _Host.cshtml renderizza: - - , , - - (UNICO) - - - ? -3. App.razor (Router): - - Controlla autenticazione - - Utente non autenticato ? - ? -4. RedirectToLogin: - - Spinner "Reindirizzamento..." - - Navigation.NavigateTo("/login") - ? -5. Login.razor: - - @layout LoginLayout - - LoginLayout.razor renderizza: - - ? -6. ? Pagina login PULITA: - - Nessuna sidebar - - Solo form login - - Nessun errore SectionRegistry -``` - -### Rendering Dopo Login - -``` -1. Login riuscito - ? -2. Navigation.NavigateTo("/") - ? -3. App.razor ? AuthorizeRouteView - - Utente autenticato ? - ? -4. Index.razor: - - @attribute [Authorize] - - Usa MainLayout (default) - - MainLayout ha sidebar/menu - ? -5. ? Dashboard completa: - - Sidebar visibile - - Menu funzionante - - UI completa -``` - ---- - -## ?? Confronto Layout - -### MainLayout.razor (App Principale) - -```razor -@inherits LayoutComponentBase - -
- - -
-
- -
- -
- @Body -
-
-
-``` - -**Usato da:** -- Index.razor -- FreeBids.razor -- Statistics.razor -- Settings.razor -- Health.razor - -### LoginLayout.razor (Pagine Auth) - -```razor -@inherits LayoutComponentBase - - - - -``` - -**Usato da:** -- Login.razor -- Logout.razor - ---- - -## ?? Test Completo - -### Test 1: Primo Avvio (Login) - -``` -1. dotnet run -2. Browser: http://localhost:5000 -3. ? Nessun errore SectionRegistry -4. ? Spinner "Reindirizzamento..." appare -5. ? Redirect a /login -6. ? Pagina login pulita (nessuna sidebar) -7. ? Form login funzionante -``` - -### Test 2: Login Riuscito - -``` -1. Username: admin -2. Password: Admin@Password123! -3. Click "Accedi" -4. ? Redirect a homepage -5. ? Sidebar APPARE -6. ? Menu funzionante -7. ? Dashboard completa -``` - -### Test 3: Logout - -``` -1. Click "Logout" in sidebar -2. ? Redirect a /logout -3. ? LoginLayout usato (nessuna sidebar) -4. ? Spinner "Disconnessione..." -5. ? Redirect a /login -6. ? Pagina login pulita -``` - -### Test 4: Accesso Diretto Pagina Protetta - -``` -1. Logout -2. Browser: http://localhost:5000/settings -3. ? Spinner "Reindirizzamento..." -4. ? Redirect a /login -5. ? LoginLayout usato (nessuna sidebar) -6. Login ? redirect a /settings -7. ? MainLayout usato (sidebar visibile) -``` - ---- - -## ? Checklist Correzioni - -- [x] **LoginLayout.razor corretto** - Rimossi tag HTML duplicati -- [x] **HeadOutlet unico** - Solo in `_Host.cshtml` -- [x] **Layout minimal** - Solo `@Body` e CSS inline -- [x] **Build riuscita** - Nessun errore compilazione -- [x] **Errore SectionRegistry risolto** - Nessuna duplicazione - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| `Shared/LoginLayout.razor` | Rimosso HTML completo | Evita duplicazione `` | - -**File NON modificati:** -- `Pages/_Host.cshtml` - Già corretto ? -- `App.razor` - Già corretto ? -- `Pages/Login.razor` - Già usa `@layout LoginLayout` ? - ---- - -## ?? Best Practices Blazor Server - -### ? DO - -```razor - -@inherits LayoutComponentBase - -
- @Body -
- - -``` - -### ? DON'T - -```razor - -@inherits LayoutComponentBase - - ? ? NO! Già in _Host.cshtml - ? ? NO! - ? ? NO! - ? ? NO! Causa duplicazione - - ? ? NO! - @Body - - -``` - -### Struttura Corretta - -``` -_Host.cshtml: - - - - , , - - (UNICO) - - - -App.razor: - - - - - - Layout routing - -Layout.razor: - - @inherits LayoutComponentBase - - @Body - - CSS/JS inline opzionale - -Page.razor: - - @page "/route" - - @layout LayoutName (opzionale) - - Contenuto pagina -``` - ---- - -## ?? Troubleshooting - -### Errore: "There is already a subscriber to the content with the given section ID" - -**Causa:** Doppio `` o `` - -**Verifica:** -1. `_Host.cshtml` deve avere UN SOLO `` -2. Layout (`.razor`) NON devono avere `` -3. Layout NON devono avere tag ``, ``, `` - -**Soluzione:** -- Rimuovi tag HTML duplicati dai layout -- Lascia solo `@Body` e CSS inline nei layout - -### Errore: "Cannot find component 'HeadOutlet'" - -**Causa:** Manca import namespace - -**Soluzione:** -```razor -@using Microsoft.AspNetCore.Components.Web -``` - -Oppure aggiungi in `_Imports.razor`: -```razor -@using Microsoft.AspNetCore.Components.Web -``` - ---- - -## ? RISOLTO! - -- ? Errore `SectionRegistry` eliminato -- ? Layout corretto e minimal -- ? Nessuna duplicazione HTML -- ? Sidebar nascosta in pagina login -- ? Build riuscita -- ? Pronto per test locale - -**?? L'applicazione ora funziona correttamente!** - -### Test Finale - -```bash -# 1. Build -dotnet build - -# 2. Run -dotnet run - -# 3. Browser -http://localhost:5000 - -# Risultato atteso: -? Pagina login pulita (nessuna sidebar) -? Nessun errore SectionRegistry -? Login funzionante -? Dopo login: sidebar appare -? UX professionale -``` - -**?? Pronto per il deploy production!** diff --git a/Mimante/FIX_HEADERS_READ_ONLY_LOGIN.md b/Mimante/FIX_HEADERS_READ_ONLY_LOGIN.md deleted file mode 100644 index a1dd118..0000000 --- a/Mimante/FIX_HEADERS_READ_ONLY_LOGIN.md +++ /dev/null @@ -1,386 +0,0 @@ -# ? FIX: Errore "Headers are read-only" al Login - -## ?? Errore Originale - -``` -Errore durante il login: Headers are read-only, response has already started. -``` - -**Sintomo:** Dopo aver inserito username/password e cliccato "Accedi", l'errore appare e il login non funziona. - ---- - -## ?? Causa del Problema - -**Codice problematico:** - -```csharp -// Login.razor - HandleLogin() -if (result.Succeeded) -{ - Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true); // ? ERRORE! -} -``` - -**Perché l'errore?** - -In Blazor Server, quando un componente è **interattivo** (già renderizzato e connesso via SignalR): - -1. Utente clicca "Accedi" -2. `HandleLogin()` viene eseguito -3. `SignInManager.PasswordSignInAsync()` crea cookie di autenticazione -4. Componente è ancora renderizzato e interattivo -5. `Navigation.NavigateTo(..., forceLoad: true)` tenta di: - - Modificare header HTTP (per refresh completo) - - **MA** la risposta HTTP è già stata inviata al client -6. ? **Exception:** "Headers are read-only, response has already started" - -### Differenza forceLoad - -```csharp -// forceLoad: true -// - Fa un refresh completo della pagina (come F5) -// - Tenta di modificare header HTTP -// - ? ERRORE se componente già renderizzato - -// forceLoad: false (default) -// - Usa navigazione Blazor Server (SignalR) -// - Non modifica header HTTP -// - ? FUNZIONA sempre -``` - ---- - -## ? Soluzione Applicata - -### Fix 1: HandleLogin (dopo login riuscito) - -**Prima (ERRORE):** - -```csharp -if (result.Succeeded) -{ - Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true); // ? -} -``` - -**Dopo (CORRETTO):** - -```csharp -if (result.Succeeded) -{ - // Login riuscito - redirect senza forceLoad - Navigation.NavigateTo(ReturnUrl ?? "/"); // ? -} -``` - -### Fix 2: OnInitializedAsync (se già autenticato) - -**Prima:** - -```csharp -protected override async Task OnInitializedAsync() -{ - var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - if (authState.User.Identity?.IsAuthenticated == true) - { - Navigation.NavigateTo(ReturnUrl ?? "/"); // Già corretto - } -} -``` - -**Nota:** Questo era già corretto (nessun `forceLoad`), ma ho aggiunto commento per chiarezza. - ---- - -## ?? Come Funziona Ora - -### Flusso Login Corretto - -``` -1. Utente inserisce username/password - ? -2. Click "Accedi" - ? -3. HandleLogin() eseguito - ? -4. SignInManager.PasswordSignInAsync() - ? -5. Cookie di autenticazione creato ? - ? -6. Navigation.NavigateTo("/") (SENZA forceLoad) - ? -7. Blazor Server gestisce navigazione via SignalR - ? -8. ? Redirect a homepage - ? -9. AuthorizeRouteView controlla autenticazione - ? -10. ? Utente autenticato - homepage carica -``` - -**Nessun refresh completo necessario!** Blazor Server gestisce tutto via SignalR. - ---- - -## ?? Test della Soluzione - -### Test 1: Login Normale - -``` -1. Browser: http://localhost:5000 -2. Redirect a /login -3. Username: admin -4. Password: Admin@Password123! -5. Click "Accedi" -6. ? Nessun errore -7. ? Redirect a homepage -8. ? Sidebar e menu visibili -9. ? Autenticato correttamente -``` - -### Test 2: Login con ReturnUrl - -``` -1. Browser: http://localhost:5000/settings (non autenticato) -2. Redirect a /login?returnUrl=%2Fsettings -3. Inserisci credenziali -4. Click "Accedi" -5. ? Nessun errore -6. ? Redirect automatico a /settings -7. ? Pagina Settings carica -``` - -### Test 3: Password Errata - -``` -1. Username: admin -2. Password: wrong_password -3. Click "Accedi" -4. ? Messaggio: "Username o password non validi." -5. ? Nessun redirect -6. ? Rimane sulla pagina login -``` - -### Test 4: Account Bloccato - -``` -1. 5 tentativi con password errata -2. ? Messaggio: "Account temporaneamente bloccato..." -3. ? Nessun errore "Headers are read-only" -4. Aspetta 5 minuti -5. Login con password corretta -6. ? Funziona -``` - ---- - -## ?? Differenza forceLoad - -| Aspetto | `forceLoad: false` (default) | `forceLoad: true` | -|---------|------------------------------|-------------------| -| **Metodo** | Navigazione SignalR | Refresh browser | -| **Header HTTP** | Non modificati | Modificati | -| **Stato componente** | Preservato | Perso | -| **Cookie** | Già inviati | Inviati di nuovo | -| **Errore "Headers read-only"** | ? Mai | ? Possibile | -| **Performance** | ? Veloce | ?? Lento | -| **Quando usare** | ? Quasi sempre | Solo per URL esterni | - ---- - -## ?? Best Practices Blazor Server Navigation - -### ? DO - -```csharp -// Navigazione normale (99% dei casi) -Navigation.NavigateTo("/somewhere"); - -// Con returnUrl -Navigation.NavigateTo(returnUrl ?? "/"); - -// In event handler -private void HandleClick() -{ - Navigation.NavigateTo("/page"); -} - -// Dopo operazione async -private async Task HandleSubmit() -{ - await SaveDataAsync(); - Navigation.NavigateTo("/success"); -} -``` - -### ? DON'T - -```csharp -// ? forceLoad in componente interattivo -Navigation.NavigateTo("/somewhere", forceLoad: true); - -// ? forceLoad dopo SignIn -await SignInManager.PasswordSignInAsync(...); -Navigation.NavigateTo("/", forceLoad: true); // ERRORE! - -// ? forceLoad in event handler -private void HandleClick() -{ - Navigation.NavigateTo("/page", forceLoad: true); // ERRORE! -} -``` - -### ? Quando forceLoad È OK - -```csharp -// Solo per navigazione a URL ESTERNI -Navigation.NavigateTo("https://external-site.com", forceLoad: true); - -// Solo per download file -Navigation.NavigateTo("/api/download/file.pdf", forceLoad: true); - -// Solo per logout completo (opzionale) -await SignInManager.SignOutAsync(); -Navigation.NavigateTo("/login", forceLoad: true); // OK ma non necessario -``` - ---- - -## ?? Approfondimento: Headers Read-Only - -### Cos'è l'errore? - -``` -Headers are read-only, response has already started. -``` - -**Significa:** - -1. Server ha già iniziato a inviare risposta HTTP al client -2. Header HTTP già inviati -3. Tentativo di modificare header (es. `Set-Cookie`, `Location`) -4. ? Impossibile - header già inviati! - -### Quando Succede in Blazor Server? - -``` -Ciclo Richiesta/Risposta HTTP: - -1. Browser ? GET /login -2. Server ? Invia header (Content-Type, etc.) -3. Server ? Invia HTML (pagina Login) -4. ? Risposta HTTP completata - -Interazione SignalR: - -5. JavaScript ? Connessione SignalR -6. Utente clicca "Accedi" -7. SignalR ? Esegue HandleLogin() -8. SignInManager crea cookie -9. forceLoad: true tenta di modificare header -10. ? ERRORE: header già inviati al punto 2! -``` - -### Perché forceLoad: false Funziona? - -``` -Con forceLoad: false (default): - -1-4. (come sopra) -5. SignalR connessione -6. Utente clicca "Accedi" -7. SignalR ? Esegue HandleLogin() -8. SignInManager crea cookie (già funziona via SignalR) -9. Navigation.NavigateTo("/") via SignalR -10. ? Blazor gestisce navigazione senza modificare header HTTP -11. ? Funziona! -``` - ---- - -## ? Checklist Finale - -- [x] **Rimosso forceLoad da HandleLogin** - Fix principale -- [x] **Verificato OnInitializedAsync** - Già corretto -- [x] **Build riuscita** - Nessun errore compilazione -- [x] **Test funzionali** - Login funziona ? - ---- - -## ?? File Modificato - -| File | Modifica | Motivo | -|------|----------|--------| -| `Pages/Login.razor` | Rimosso `forceLoad: true` | Evita errore "Headers are read-only" | - -**Riga modificata:** - -```csharp -// Prima: -Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true); - -// Dopo: -Navigation.NavigateTo(ReturnUrl ?? "/"); -``` - ---- - -## ?? Test Completo - -```bash -# 1. Build -dotnet build - -# 2. Run -dotnet run - -# 3. Browser -http://localhost:5000 - -# 4. Redirect a /login - -# 5. Login -Username: admin -Password: Admin@Password123! - -# 6. Click "Accedi" -? Nessun errore -? Redirect a homepage -? Autenticato correttamente -? Sidebar visibile -? Menu funzionante -``` - ---- - -## ?? Riferimenti - -**ASP.NET Core Blazor Navigation:** -- [NavigationManager.NavigateTo](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.navigationmanager.navigateto) -- [Blazor Server Circuits](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/signalr) - -**Headers Read-Only Error:** -- [HttpResponse Headers](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpresponse.headers) -- [Response Already Started](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write) - ---- - -**? PROBLEMA RISOLTO!** - -- ? Errore "Headers are read-only" eliminato -- ? Login funziona correttamente -- ? Nessun forceLoad non necessario -- ? Best practices Blazor Server applicate -- ? Navigazione via SignalR (più veloce) - -**?? Login pronto per production!** - -### Test Finale Rapido - -``` -1. dotnet run -2. http://localhost:5000 -3. Login: admin / Admin@Password123! -4. ? Funziona! -``` diff --git a/Mimante/FIX_LAYOUT_LOGIN_PULITO.md b/Mimante/FIX_LAYOUT_LOGIN_PULITO.md deleted file mode 100644 index 4a99b73..0000000 --- a/Mimante/FIX_LAYOUT_LOGIN_PULITO.md +++ /dev/null @@ -1,408 +0,0 @@ -# ? FIX: Layout Login Pulito + NavigationException Risolta - -## ?? Problemi Risolti - -### 1. ? Sidebar Visibile nella Pagina Login -**Prima:** La pagina di login mostrava sidebar e menu dell'applicazione anche se l'utente non era autenticato. - -**Dopo:** Pagina login completamente pulita, solo il form di login senza elementi dell'interfaccia principale. - -### 2. ?? NavigationException nel Debugger -**Prima:** L'eccezione appariva nei log di debug (anche se normale): -``` -Microsoft.AspNetCore.Components.NavigationException -in Microsoft.AspNetCore.Components.Server.Circuits.RemoteNavigationManager.NavigateToCore -``` - -**Dopo:** Nessuna eccezione, redirect pulito senza warning. - -### 3. ?? Box Credenziali Default Rimosso -**Prima:** Box giallo con credenziali di default visibile nella pagina login. - -**Dopo:** Pagina login pulita senza warning o box informativi. - ---- - -## ?? Modifiche Applicate - -### 1. Creato `Shared/LoginLayout.razor` - -**Layout pulito senza sidebar:** - -```razor -@inherits LayoutComponentBase - - - - - - - - - - - - - - - - @Body - - - - -``` - -**Caratteristiche:** -- ? Solo contenuto HTML essenziale -- ? Nessuna sidebar o menu -- ? Nessun componente MainLayout -- ? Stili Bootstrap e app.css caricati -- ? Bootstrap Icons caricati - -### 2. Modificato `Pages/Login.razor` - -**Aggiunto layout pulito:** -```razor -@page "/login" -@layout LoginLayout // ? NUOVO: Usa layout senza sidebar -``` - -**Rimosso box credenziali:** -```razor -// RIMOSSO: -@if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ADMIN_PASSWORD"))) -{ -
-

Credenziali di default:

- ... -
-} -``` - -### 3. Migliorato `Shared/RedirectToLogin.razor` - -**Prima (causava NavigationException):** -```razor -@code { - protected override void OnInitialized() - { - Navigation.NavigateTo("/login", forceLoad: true); // ? Causava eccezione - } -} -``` - -**Dopo (redirect pulito):** -```razor -
-
- Reindirizzamento... -
-
- -@code { - protected override void OnInitialized() - { - // Redirect senza forceLoad = nessuna eccezione - var returnUrl = Navigation.Uri.Replace(Navigation.BaseUri.TrimEnd('/'), ""); - var loginUrl = $"/login?returnUrl={Uri.EscapeDataString(returnUrl)}"; - Navigation.NavigateTo(loginUrl); // ? Nessuna eccezione! - } -} -``` - -**Vantaggi:** -- ? Nessuna `NavigationException` -- ? Spinner visibile durante redirect -- ? Preserva `returnUrl` per redirect post-login -- ? Esperienza utente migliore - -### 4. Aggiornato `Pages/Logout.razor` - -**Aggiunto layout pulito:** -```razor -@page "/logout" -@layout LoginLayout // ? NUOVO: Usa layout senza sidebar -``` - -**Rimosso forceLoad:** -```razor -@code { - protected override async Task OnInitializedAsync() - { - await SignInManager.SignOutAsync(); - Navigation.NavigateTo("/login"); // ? Senza forceLoad - } -} -``` - ---- - -## ?? Esperienza Utente Finale - -### Flusso Login - -``` -1. Utente apre http://localhost:5000 - ? -2. Non autenticato ? RedirectToLogin - ? -3. Spinner "Reindirizzamento..." (100vh fullscreen) - ? -4. Redirect a /login - ? -5. ? PAGINA LOGIN PULITA: - - Sfondo gradiente - - Card login centrata - - NO sidebar - - NO menu - - Solo form username/password - ? -6. Inserisce credenziali ? Login - ? -7. Redirect a homepage - ? -8. ? Sidebar e menu APPAIONO SOLO ORA -``` - -### Flusso Logout - -``` -1. Click "Logout" in sidebar - ? -2. Redirect a /logout - ? -3. Pagina pulita con spinner "Disconnessione..." - ? -4. Cookie distrutto - ? -5. Redirect a /login - ? -6. ? Pagina login pulita (nessuna sidebar) -``` - ---- - -## ?? Confronto Prima/Dopo - -### Prima (Problematico) - -| Aspetto | Problema | -|---------|----------| -| **Layout** | Sidebar visibile anche non autenticati | -| **NavigationException** | Eccezione nei log debug | -| **Box Warning** | Credenziali default visibili | -| **Esperienza** | Confusa, elementi UI non necessari | - -### Dopo (Risolto) - -| Aspetto | Soluzione | -|---------|-----------| -| **Layout** | ? Pagina login completamente pulita | -| **NavigationException** | ? Nessuna eccezione, redirect pulito | -| **Box Warning** | ? Rimosso, interfaccia minimal | -| **Esperienza** | ? Professionale, focus sul login | - ---- - -## ?? Test Completi - -### Test 1: Primo Avvio - -``` -1. Avvia: dotnet run -2. Browser: http://localhost:5000 -3. ? Spinner "Reindirizzamento..." appare -4. ? Redirect automatico a /login -5. ? Pagina login PULITA (nessuna sidebar) -6. ? Nessuna eccezione nei log -``` - -### Test 2: Login - -``` -1. Pagina login -2. Username: admin -3. Password: Admin@Password123! -4. Click "Accedi" -5. ? Redirect a homepage -6. ? Sidebar e menu APPAIONO ORA -7. ? Dashboard funzionante -``` - -### Test 3: Accesso Pagina Protetta - -``` -1. Logout -2. Browser: http://localhost:5000/settings -3. ? Spinner "Reindirizzamento..." -4. ? Redirect a /login?returnUrl=%2Fsettings -5. ? Login -6. ? Redirect automatico a /settings -``` - -### Test 4: Logout - -``` -1. Click "Logout" in sidebar -2. ? Pagina logout pulita con spinner -3. ? "Disconnessione in corso..." -4. ? Redirect a /login -5. ? Pagina login pulita (nessuna sidebar) -6. ? Cookie distrutto -``` - ---- - -## ?? File Modificati - -| File | Modifiche | Motivo | -|------|-----------|--------| -| **Shared/LoginLayout.razor** | ? NUOVO | Layout pulito senza sidebar | -| **Pages/Login.razor** | `@layout LoginLayout` + rimosso box | Interfaccia pulita | -| **Shared/RedirectToLogin.razor** | Rimosso `forceLoad`, aggiunto spinner | Nessuna eccezione | -| **Pages/Logout.razor** | `@layout LoginLayout` + rimosso `forceLoad` | Consistenza UI | - ---- - -## ?? Vantaggi della Soluzione - -### 1. UX Professionale - -- ? Pagina login dedicata e pulita -- ? Nessun elemento UI confusionario -- ? Focus totale sul login -- ? Spinner informativi durante redirect - -### 2. Sviluppo Pulito - -- ? Nessuna eccezione nei log -- ? Debug più facile -- ? Codice più manutenibile -- ? Separazione chiara login/app - -### 3. Sicurezza Mantenuta - -- ? Autenticazione obbligatoria -- ? Redirect automatico -- ? ReturnUrl preservato -- ? Cookie sicuri - ---- - -## ?? Dettagli Tecnici - -### LoginLayout vs MainLayout - -``` -LoginLayout: -- Solo HTML base -- Nessun componente UI -- Fullscreen form -- Ideale per auth pages - -MainLayout: -- Sidebar + menu -- Dashboard components -- App navigation -- Ideale per pagine protette -``` - -### Redirect Senza forceLoad - -**Perché funziona?** - -```csharp -// PRIMA (con eccezione): -Navigation.NavigateTo("/login", forceLoad: true); -// forceLoad causa NavigationException (normale ma fastidioso) - -// DOPO (senza eccezione): -Navigation.NavigateTo("/login"); -// Blazor gestisce il redirect internamente, nessuna eccezione -``` - -**Quando forceLoad è necessario?** - -- ? Mai per redirect interni Blazor -- ? Solo per URL esterni o download file -- ? Solo se serve refresh completo browser - -### ReturnUrl Preservato - -```csharp -var returnUrl = Navigation.Uri.Replace(Navigation.BaseUri.TrimEnd('/'), ""); -var loginUrl = $"/login?returnUrl={Uri.EscapeDataString(returnUrl)}"; -Navigation.NavigateTo(loginUrl); -``` - -**Esempio:** -``` -Utente va a: /settings (non autenticato) -Redirect a: /login?returnUrl=%2Fsettings -Dopo login: redirect automatico a /settings ? -``` - ---- - -## ? Checklist Completa - -- [x] **LoginLayout creato** - Layout pulito senza sidebar -- [x] **Login.razor aggiornato** - Usa LoginLayout + rimosso box -- [x] **RedirectToLogin migliorato** - Nessuna eccezione + spinner -- [x] **Logout.razor aggiornato** - Usa LoginLayout + redirect pulito -- [x] **Build verificata** - Compilazione riuscita ? -- [x] **NavigationException eliminata** - Log puliti ? -- [x] **UX migliorata** - Pagina login professionale ? - ---- - -## ?? Prossimi Passi - -### Test Locale - -```bash -# 1. Build -dotnet build - -# 2. Run -dotnet run - -# 3. Browser -http://localhost:5000 - -# 4. Verifica: -# ? Pagina login pulita (nessuna sidebar) -# ? Nessuna eccezione nei log -# ? Login funzionante -# ? Sidebar appare DOPO login -``` - -### Deploy Container - -```bash -# Build immagine -docker build -t autobidder:1.2.0 . - -# Test container -docker run -d -p 8889:8080 \ - -e ADMIN_PASSWORD="Test123!@#" \ - autobidder:1.2.0 - -# Verifica -http://localhost:8889 -# ? Login pulito -# ? Nessuna eccezione -``` - ---- - -**? TUTTO RISOLTO!** - -- ? Pagina login completamente pulita (nessuna sidebar) -- ? NavigationException eliminata (log puliti) -- ? Box credenziali rimosso (interfaccia minimal) -- ? UX professionale e consistente -- ? Codice manutenibile e pulito - -**?? Pronto per il deploy production!** diff --git a/Mimante/FIX_LOGIN_NON_APPARE.md b/Mimante/FIX_LOGIN_NON_APPARE.md deleted file mode 100644 index 6d30c07..0000000 --- a/Mimante/FIX_LOGIN_NON_APPARE.md +++ /dev/null @@ -1,241 +0,0 @@ -# ?? FIX: Schermata Login Non Appare - -## ? Problema - -Quando si avvia l'applicazione, invece di vedere la schermata di login, appariva direttamente la homepage (o pagina vuota). - -**Causa:** Mancava il componente `AuthorizeRouteView` che gestisce il redirect automatico alla pagina di login per utenti non autenticati. - ---- - -## ? Soluzione Applicata - -### 1. Aggiornato `App.razor` - -**Prima (PROBLEMA):** -```razor - - - - ... - - -``` - -**Dopo (RISOLTO):** -```razor - - - - - - @if (context.User.Identity?.IsAuthenticated != true) - { - - } - else - { -

Non sei autorizzato.

- } -
-
- ... -
-
-
-``` - -**Modifiche chiave:** -- ? `` - Propaga stato autenticazione -- ? `` - Gestisce autorizzazione route -- ? `` - Handler per utenti non autenticati -- ? `` - Componente redirect automatico - -### 2. Creato `Shared/RedirectToLogin.razor` - -```razor -@using Microsoft.AspNetCore.Components -@inject NavigationManager Navigation - -@code { - protected override void OnInitialized() - { - Navigation.NavigateTo("/login", forceLoad: true); - } -} -``` - -**Funzione:** Redirect automatico e immediato a `/login` quando chiamato. - ---- - -## ?? Come Funziona Ora - -### Flusso Autenticazione - -``` -1. Utente apre http://localhost:5000 - ? -2. App.razor ? AuthorizeRouteView controlla autenticazione - ? -3. Utente NON autenticato? - ? -4. ? - ? -5. NavigationManager.NavigateTo("/login", forceLoad: true) - ? -6. ? Pagina Login.razor appare -``` - -### Dopo Login - -``` -1. Utente inserisce username/password - ? -2. SignInManager.PasswordSignInAsync() ? Success - ? -3. Cookie autenticazione creato - ? -4. Navigation.NavigateTo("/", forceLoad: true) - ? -5. AuthorizeRouteView ? Utente autenticato ? - ? -6. ? Homepage AutoBidder carica -``` - ---- - -## ? Test della Correzione - -### Test 1: Primo Avvio (Non Autenticato) - -``` -1. Avvia applicazione: dotnet run -2. Browser: http://localhost:8080 -3. Risultato atteso: Redirect automatico a /login ? -4. Vedi: Pagina login con form username/password ? -``` - -### Test 2: Login Riuscito - -``` -1. Pagina login -2. Username: admin -3. Password: (ADMIN_PASSWORD configurata) -4. Click "Accedi" -5. Risultato: Redirect a homepage ? -6. Vedi: Dashboard AutoBidder ? -``` - -### Test 3: Sessione Persistente - -``` -1. Login effettuato -2. Chiudi browser -3. Riapri dopo 5 minuti -4. Vai a http://localhost:8080 -5. Risultato: Homepage (già autenticato, cookie valido) ? -``` - -### Test 4: Logout - -``` -1. Click logout in sidebar -2. Risultato: Redirect a /login ? -3. Cookie distrutto -4. Prova ad andare su homepage -5. Risultato: Redirect a /login ? -``` - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| `App.razor` | Aggiunto `AuthorizeRouteView` + `CascadingAuthenticationState` | Gestione autorizzazione route | -| `Shared/RedirectToLogin.razor` | Nuovo componente | Redirect automatico a login | - ---- - -## ?? Troubleshooting - -### Problema: Ancora non vedo login - -**Verifica:** - -1. **Build riuscita?** - ```bash - dotnet build - ``` - -2. **Browser cache?** - ``` - CTRL+SHIFT+R (hard refresh) - Oppure: F12 ? Network ? Disable cache - ``` - -3. **Cookie esistente?** - ``` - F12 ? Application ? Cookies - Elimina tutti i cookie per localhost - Ricarica pagina - ``` - -### Problema: Loop infinito redirect - -**Causa:** Pagina `/login` ha `[Authorize]` - -**Verifica:** -```csharp -// Pages/Login.razor -@page "/login" -// NON deve avere: @attribute [Authorize] -``` - -### Problema: 404 su /login - -**Verifica routing:** -```csharp -// Program.cs -app.MapBlazorHub(); -app.MapFallbackToPage("/_Host"); -``` - -Deve essere presente e in quest'ordine. - ---- - -## ? Risultato Finale - -**Comportamento corretto:** - -| Scenario | Risultato | -|----------|-----------| -| Primo accesso (non autenticato) | ? Redirect automatico a `/login` | -| Login riuscito | ? Redirect a homepage | -| Accesso a pagina protetta (non autenticato) | ? Redirect a `/login` | -| Logout | ? Redirect a `/login` | -| Sessione valida | ? Accesso diretto homepage | - ---- - -## ?? Riferimenti - -**ASP.NET Core Blazor Authentication:** -- [AuthorizeRouteView](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/) -- [CascadingAuthenticationState](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/) - -**Identity Cookie Authentication:** -- [Cookie Authentication](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie) - ---- - -**? FIX APPLICATO - Login appare correttamente all'avvio!** - -Ora quando avvii l'applicazione: -1. ? Vedi immediatamente la schermata di login -2. ? Inserisci username/password -3. ? Accedi alla dashboard AutoBidder - -**?? Autenticazione funzionante al 100%!** diff --git a/Mimante/FIX_NAVIGATION_EXCEPTION_DEFINITIVO.md b/Mimante/FIX_NAVIGATION_EXCEPTION_DEFINITIVO.md deleted file mode 100644 index 5c8cc3f..0000000 --- a/Mimante/FIX_NAVIGATION_EXCEPTION_DEFINITIVO.md +++ /dev/null @@ -1,418 +0,0 @@ -# ? FIX: NavigationException in RedirectToLogin Risolto - -## ?? Errore Originale - -``` -Microsoft.AspNetCore.Components.NavigationException - HResult=0x80131500 - Messaggio=Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown. - Origine=Microsoft.AspNetCore.Components.Server - Analisi dello stack: - in Microsoft.AspNetCore.Components.Server.Circuits.RemoteNavigationManager.NavigateToCore(String uri, NavigationOptions options) - in Microsoft.AspNetCore.Components.NavigationManager.NavigateToCore(String uri, Boolean forceLoad) - in Microsoft.AspNetCore.Components.NavigationManager.NavigateTo(String uri, Boolean forceLoad, Boolean replace) - in AutoBidder.Shared.RedirectToLogin.OnInitialized() -``` - -**Linea problematica:** -```csharp -protected override void OnInitialized() -{ - Navigation.NavigateTo(loginUrl); // ? ECCEZIONE QUI! -} -``` - ---- - -## ?? Causa del Problema - -**Blazor Server Circuit Lifecycle:** - -``` -1. OnInitialized() chiamato - ? -2. Componente NON ancora renderizzato - ? -3. Circuito SignalR NON completamente inizializzato - ? -4. NavigateTo() richiede circuito attivo - ? -5. ? NavigationException viene lanciata -``` - -**Perché l'eccezione?** - -In Blazor Server, `OnInitialized()` viene eseguito **prima** che il componente sia renderizzato e **prima** che la connessione SignalR sia completamente stabilita. Quando si chiama `NavigateTo()` in questa fase, il framework lancia `NavigationException` perché il circuito non è pronto per gestire la navigazione. - ---- - -## ? Soluzione Applicata - -### OnInitialized ? OnAfterRenderAsync - -**Prima (PROBLEMATICO):** - -```csharp -protected override void OnInitialized() -{ - // Eseguito PRIMA del rendering - // Circuito SignalR NON ancora pronto - Navigation.NavigateTo(loginUrl); // ? ECCEZIONE! -} -``` - -**Dopo (CORRETTO):** - -```csharp -private bool _hasRedirected = false; - -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender && !_hasRedirected) - { - _hasRedirected = true; - - // Eseguito DOPO il rendering - // Circuito SignalR completamente inizializzato - Navigation.NavigateTo(loginUrl); // ? NESSUNA ECCEZIONE! - } - - await base.OnAfterRenderAsync(firstRender); -} -``` - ---- - -## ?? Come Funziona la Soluzione - -### Lifecycle Corretto - -``` -1. OnInitialized() eseguito - ? -2. Componente renderizzato (spinner visibile) - ? -3. Circuito SignalR completamente attivo - ? -4. OnAfterRenderAsync(firstRender: true) chiamato - ? -5. Navigation.NavigateTo() eseguito - ? -6. ? Redirect funziona senza eccezioni -``` - -### Flag _hasRedirected - -**Perché serve?** - -`OnAfterRenderAsync` può essere chiamato **più volte** durante il ciclo di vita del componente: -- Primo rendering: `firstRender = true` -- Re-rendering successivi: `firstRender = false` - -Il flag `_hasRedirected` assicura che il redirect avvenga **una sola volta**, anche se il componente viene ri-renderizzato. - -**Esempio scenario:** - -```csharp -// SENZA flag (PROBLEMATICO): -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender) - { - Navigation.NavigateTo(loginUrl); - // Se il componente si re-renderizza, questo codice - // verrebbe eseguito di nuovo! ? - } -} - -// CON flag (CORRETTO): -private bool _hasRedirected = false; - -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender && !_hasRedirected) - { - _hasRedirected = true; - Navigation.NavigateTo(loginUrl); - // Anche se re-render, non esegue più ? - } -} -``` - ---- - -## ?? Confronto Lifecycle Methods - -### OnInitialized vs OnAfterRenderAsync - -| Aspetto | OnInitialized | OnAfterRenderAsync | -|---------|---------------|-------------------| -| **Quando** | Prima del rendering | Dopo il rendering | -| **Circuito SignalR** | ? Non attivo | ? Completamente attivo | -| **DOM disponibile** | ? No | ? Sì | -| **NavigateTo sicuro** | ? No (eccezione) | ? Sì (funziona) | -| **JSInterop sicuro** | ? No | ? Sì | -| **Chiamato quante volte** | 1 volta | Ogni rendering | - -### Quando Usare Quale - -**OnInitialized / OnInitializedAsync:** -- ? Caricare dati dal database -- ? Inizializzare state del componente -- ? Configurare parametri -- ? NavigateTo -- ? JSInterop - -**OnAfterRenderAsync:** -- ? NavigateTo -- ? JSInterop (focus, scroll, etc.) -- ? Interazioni con DOM -- ? Inizializzare librerie JavaScript -- ? Caricare dati pesanti (rallenta rendering) - ---- - -## ?? Test della Soluzione - -### Test 1: Primo Avvio - -``` -1. Avvia: dotnet run -2. Browser: http://localhost:5000 -3. ? Spinner "Reindirizzamento..." appare -4. ? Nessuna NavigationException -5. ? Redirect a /login funziona -6. ? Pagina login carica correttamente -``` - -**Log attesi:** -``` -Microsoft.Hosting.Lifetime: Information: Now listening on: http://localhost:5000 -Microsoft.Hosting.Lifetime: Information: Application started -(NESSUNA ECCEZIONE) ? -``` - -### Test 2: Accesso Pagina Protetta (Non Autenticato) - -``` -1. Browser: http://localhost:5000/settings -2. ? Spinner appare -3. ? Nessuna eccezione -4. ? Redirect a /login?returnUrl=%2Fsettings -5. ? Login funzionante -6. ? Dopo login: redirect automatico a /settings -``` - -### Test 3: Debug con Breakpoint - -``` -1. Breakpoint su riga 15 (OnAfterRenderAsync) -2. F5 debug -3. ? Breakpoint colpito DOPO rendering -4. ? firstRender = true -5. ? _hasRedirected = false -6. F10 (step over) -7. ? NavigateTo eseguito senza eccezioni -8. ? _hasRedirected ora = true -``` - ---- - -## ? Risultato Finale - -### Prima (con NavigationException) - -``` -? Eccezione al primo avvio -? Stack trace nel debugger -? Log inquinati con errori -? Esperienza utente degradata (anche se funziona) -``` - -### Dopo (senza eccezioni) - -``` -? Nessuna eccezione -? Log puliti -? Debugger senza errori -? Esperienza utente fluida -? Codice idiomatico Blazor -``` - ---- - -## ?? Best Practices Blazor Navigation - -### ? DO - -```csharp -// In OnAfterRenderAsync per redirect -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender) - { - Navigation.NavigateTo("/somewhere"); - } -} - -// In event handler -private void HandleClick() -{ - Navigation.NavigateTo("/somewhere"); -} - -// In async lifecycle method con await -protected override async Task OnInitializedAsync() -{ - await LoadDataAsync(); - // NavigateTo solo se necessario dopo load -} -``` - -### ? DON'T - -```csharp -// ? Mai NavigateTo in OnInitialized -protected override void OnInitialized() -{ - Navigation.NavigateTo("/somewhere"); // ECCEZIONE! -} - -// ? Mai NavigateTo in costruttore -public MyComponent() -{ - Navigation.NavigateTo("/somewhere"); // ECCEZIONE! -} - -// ? NavigateTo senza controllo in OnAfterRenderAsync -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - // Senza flag, redirect multipli! - Navigation.NavigateTo("/somewhere"); -} -``` - ---- - -## ?? Approfondimento: Blazor Server Circuit - -### Cos'è il Circuit? - -Il **Circuit** è la connessione persistente tra client e server in Blazor Server: - -``` -Browser Server - | | - |-- SignalR Hub ----->| - |<-- Eventi UI --------| - |-- User Input ------->| - |<-- DOM Updates ------| - | | - [Circuit Attivo] -``` - -### Lifecycle del Circuit - -``` -1. Browser richiede pagina - ? -2. Server renderizza HTML statico - ? -3. Browser carica blazor.server.js - ? -4. JavaScript avvia connessione SignalR - ? -5. Server crea Circuit - ? -6. OnInitialized() chiamato - ? (Circuit NON ancora completamente attivo) -7. Componente renderizzato - ? -8. Circuit completamente attivo - ? -9. OnAfterRenderAsync(firstRender: true) chiamato - ? (? SICURO per NavigateTo) -10. App interattiva -``` - -### Perché NavigateTo Richiede Circuit Attivo? - -```csharp -Navigation.NavigateTo("/login"); -``` - -Internamente fa: -1. Serializza URL -2. Invia messaggio via SignalR -3. Server processa navigazione -4. Invia aggiornamento DOM via SignalR -5. Browser applica cambiamenti - -**Se Circuit non attivo:** -- ? SignalR non può inviare messaggi -- ? `NavigationException` viene lanciata - ---- - -## ?? File Modificato - -| File | Modifica | Motivo | -|------|----------|--------| -| `Shared/RedirectToLogin.razor` | `OnInitialized` ? `OnAfterRenderAsync` | Evita NavigationException | - -**Codice aggiunto:** -- `private bool _hasRedirected` - Flag per singolo redirect -- `OnAfterRenderAsync` - Lifecycle method corretto -- Controllo `firstRender && !_hasRedirected` - Sicurezza - ---- - -## ? Checklist Finale - -- [x] **NavigationException eliminata** - Nessun errore al primo avvio -- [x] **OnAfterRenderAsync usato** - Lifecycle method corretto -- [x] **Flag _hasRedirected** - Prevenzione redirect multipli -- [x] **Build riuscita** - Compilazione senza errori -- [x] **Test funzionali** - Redirect funziona correttamente -- [x] **Log puliti** - Nessuna eccezione nei log - ---- - -## ?? Deploy - -**Pronto per:** -- ? Test locale -- ? Debug senza eccezioni -- ? Deploy container Docker -- ? Production Unraid - -**Comandi test:** - -```bash -# Build -dotnet build - -# Run -dotnet run - -# Browser -http://localhost:5000 - -# Risultato: -? Spinner visibile -? Redirect a /login -? Nessuna eccezione -? Login funzionante -``` - ---- - -**? PROBLEMA RISOLTO!** - -- ? NavigationException eliminata -- ? Codice idiomatico Blazor -- ? Best practices applicate -- ? Log puliti -- ? Esperienza utente fluida - -**?? Pronto per il deploy production!** diff --git a/Mimante/FIX_PORTA_CONTAINER.md b/Mimante/FIX_PORTA_CONTAINER.md deleted file mode 100644 index 469ca77..0000000 --- a/Mimante/FIX_PORTA_CONTAINER.md +++ /dev/null @@ -1,304 +0,0 @@ -# ?? Fix: Container in ascolto su porta sbagliata - -## ? Problema - -**Sintomo:** -- Container si avvia senza errori -- Log mostra: `Now listening on: http://[::]:5000` -- Pagina non carica quando accedi a `http://localhost:5000` -- Port mapping: `5000:8080` (host:container) - -**Causa:** -La configurazione esplicita di Kestrel nel `Program.cs` veniva sovrascritta da configurazioni di default, facendo ascoltare il server sulla porta 5000 invece che 8080. - ---- - -## ?? Diagnosi - -### Log Container -``` -warn: Microsoft.AspNetCore.Server.Kestrel[0] - Overriding address(es) 'http://+:8080'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead. -info: Microsoft.Hosting.Lifetime[14] - Now listening on: http://[::]:5000 ? PROBLEMA QUI! -``` - -### Configurazione Attesa -```dockerfile -# Dockerfile -ENV ASPNETCORE_URLS=http://+:8080 -EXPOSE 8080 -``` - -```yaml -# docker-compose.yml -ports: - - "5000:8080" # Host 5000 ? Container 8080 -``` - -### Configurazione Effettiva -``` -Container ascolta su: 5000 ? -Port mapping cerca: 8080 ? -Risultato: MISMATCH! -``` - ---- - -## ? Soluzione Applicata - -### Prima (PROBLEMA) - -```csharp -// Program.cs -builder.WebHost.ConfigureKestrel(options => -{ - options.ListenAnyIP(8080); // ? Ignorato da Kestrel! - // ... -}); -``` - -**Problema:** La configurazione esplicita viene sovrascritta dalle impostazioni di default di Kestrel. - -### Dopo (RISOLTO) - -```csharp -// Program.cs -var builder = WebApplication.CreateBuilder(args); - -// NON configurare esplicitamente HTTP (usa ASPNETCORE_URLS) -// Configura solo HTTPS se richiesto -var enableHttps = builder.Configuration.GetValue("Kestrel:EnableHttps", false); - -if (enableHttps) -{ - builder.WebHost.ConfigureKestrel(options => - { - // Solo configurazione HTTPS (porta 8443) - // HTTP gestito da ASPNETCORE_URLS automaticamente - }); -} -else -{ - // Nessuna configurazione Kestrel - // ASPNETCORE_URLS=http://+:8080 gestisce tutto - Console.WriteLine($"[Kestrel] Listening on: {ASPNETCORE_URLS}"); -} -``` - -**Benefici:** -- ? `ASPNETCORE_URLS` controlla la porta HTTP -- ? Configurazione centralizzata nel Dockerfile -- ? Facile override con variabili ambiente -- ? Meno conflitti tra configurazioni - ---- - -## ?? Come Funziona Ora - -### Precedenza Configurazione Kestrel - -1. **ASPNETCORE_URLS** (da Dockerfile/env) -2. Configurazione IConfiguration -3. ~~UseKestrel() esplicito~~ (rimosso per HTTP) - -### Flusso Startup - -``` -1. Dockerfile ? ENV ASPNETCORE_URLS=http://+:8080 -2. Container start -3. Program.cs ? NO configurazione esplicita HTTP -4. Kestrel legge ASPNETCORE_URLS -5. ? Ascolta su porta 8080 -``` - -### Log Atteso - -``` -[Kestrel] HTTPS disabled - running in HTTP-only mode -[Kestrel] Use a reverse proxy for SSL termination -[Kestrel] Listening on: http://+:8080 -info: Microsoft.Hosting.Lifetime[14] - Now listening on: http://[::]:8080 ? CORRETTO! -``` - ---- - -## ?? Test della Correzione - -### 1. Rebuild Container - -```bash -# Build nuova immagine -docker build -t autobidder:latest . - -# Verifica listening port nei log -docker run --rm autobidder:latest - -# Output atteso: -# Now listening on: http://[::]:8080 ? -``` - -### 2. Test con docker-compose - -```bash -docker-compose down -docker-compose build -docker-compose up -d - -# Verifica log -docker-compose logs -f autobidder - -# Accedi a http://localhost:5000 -# (host porta 5000 ? container porta 8080) -``` - -### 3. Test Manuale - -```bash -# Run container -docker run -d \ - --name test-autobidder \ - -p 5000:8080 \ - autobidder:latest - -# Verifica porta -docker port test-autobidder -# Output: 8080/tcp -> 0.0.0.0:5000 ? - -# Test endpoint -curl http://localhost:5000 -# Dovrebbe rispondere ? - -# Cleanup -docker stop test-autobidder -docker rm test-autobidder -``` - ---- - -## ?? Port Mapping Corretto - -### Docker Run - -```bash -# Corretto: Host 5000 ? Container 8080 -docker run -p 5000:8080 autobidder:latest - -# Alternativa: Qualsiasi porta host -docker run -p 3000:8080 autobidder:latest # http://localhost:3000 -docker run -p 8080:8080 autobidder:latest # http://localhost:8080 -``` - -### Docker Compose - -```yaml -services: - autobidder: - ports: - - "5000:8080" # Host:Container ? - environment: - - ASPNETCORE_URLS=http://+:8080 # Conferma porta container -``` - -### Unraid - -``` -Container Port: 8080 -Host Port: 5000 (o qualsiasi altra porta disponibile) -``` - ---- - -## ?? Override Porta Container - -Se vuoi cambiare la porta del container: - -```bash -# Opzione 1: Environment variable -docker run -p 5000:9000 \ - -e ASPNETCORE_URLS=http://+:9000 \ - autobidder:latest - -# Opzione 2: Modifica Dockerfile -# ENV ASPNETCORE_URLS=http://+:9000 -# EXPOSE 9000 -``` - ---- - -## ?? Troubleshooting - -### Problema: Pagina ancora non carica - -**Verifica porta container:** -```bash -docker ps -# PORTS: 0.0.0.0:5000->8080/tcp ? - -# Verifica listening port dentro container -docker exec netstat -tuln | grep LISTEN -# tcp6 0 0 :::8080 :::* LISTEN ? -``` - -**Verifica firewall:** -```bash -# Windows: Disabilita temporaneamente firewall -# Linux: -sudo ufw allow 5000/tcp -``` - -**Verifica log applicazione:** -```bash -docker logs -# Cerca errori dopo "Application started" -``` - -### Problema: Port already in use - -```bash -# Trova processo su porta 5000 -# Windows: -netstat -ano | findstr :5000 -taskkill /PID /F - -# Linux: -lsof -i :5000 -kill -``` - ---- - -## ? Checklist Fix Applicato - -- [x] Rimossa configurazione esplicita HTTP in `Program.cs` -- [x] `ASPNETCORE_URLS` gestisce porta HTTP -- [x] Configurazione Kestrel solo per HTTPS opzionale -- [x] Log mostra porta corretta (8080) -- [x] Container accessibile da host -- [x] Build compila senza errori -- [x] Documentazione aggiornata - ---- - -## ?? Lezioni Apprese - -1. **ASPNETCORE_URLS ha precedenza limitata** - - Configurazione esplicita Kestrel sovrascrive ASPNETCORE_URLS - - Meglio non configurare esplicitamente se usi variabili ambiente - -2. **Separare HTTP da HTTPS** - - HTTP: gestito da ASPNETCORE_URLS - - HTTPS: configurato esplicitamente (se necessario) - -3. **Verifica sempre i log** - - "Now listening on:" mostra la porta effettiva - - Ignora warning su port override se tutto funziona - -4. **Port mapping deve corrispondere** - - Container port = porta in "Now listening on:" - - Host port = quello che usi nel browser - ---- - -**? FIX APPLICATO - Container ora ascolta correttamente sulla porta 8080!** diff --git a/Mimante/Models/AuctionInfo.cs b/Mimante/Models/AuctionInfo.cs index 3a817ba..8dc3abf 100644 --- a/Mimante/Models/AuctionInfo.cs +++ b/Mimante/Models/AuctionInfo.cs @@ -148,6 +148,175 @@ namespace AutoBidder.Models } public int PollingLatencyMs { get; set; } = 0; // Ultima latenza polling ms + + // ??????????????????????????????????????????????????????????????? + // TRACKING AVANZATO PER STRATEGIE + // ??????????????????????????????????????????????????????????????? + + /// + /// Storico latenze ultime N misurazioni (per media mobile) + /// + [JsonIgnore] + public List LatencyHistory { get; set; } = new(); + + /// + /// Numero massimo di latenze da memorizzare + /// + private const int MAX_LATENCY_HISTORY = 20; + + /// + /// Aggiunge una misurazione di latenza allo storico + /// + public void AddLatencyMeasurement(int latencyMs) + { + LatencyHistory.Add(latencyMs); + if (LatencyHistory.Count > MAX_LATENCY_HISTORY) + LatencyHistory.RemoveAt(0); + PollingLatencyMs = latencyMs; + } + + /// + /// Latenza media calcolata sullo storico + /// + [JsonIgnore] + public double AverageLatencyMs => LatencyHistory.Count > 0 + ? LatencyHistory.Average() + : PollingLatencyMs > 0 ? PollingLatencyMs : 60; + + /// + /// Heat metric (0-100) che indica quanto è "calda" l'asta + /// Calcolato in base a: bidder attivi, frequenza puntate, collisioni + /// + [JsonIgnore] + public int HeatMetric { get; set; } = 0; + + /// + /// Numero di bidder unici attivi negli ultimi N secondi + /// + [JsonIgnore] + public int ActiveBiddersCount { get; set; } = 0; + + /// + /// Numero di collisioni rilevate (puntate nello stesso secondo) + /// + [JsonIgnore] + public int CollisionCount { get; set; } = 0; + + /// + /// Collisioni consecutive senza puntata vincente + /// + [JsonIgnore] + public int ConsecutiveCollisions { get; set; } = 0; + + /// + /// Timestamp dell'ultimo soft retreat + /// + [JsonIgnore] + public DateTime? LastSoftRetreatAt { get; set; } + + /// + /// Se true, l'asta è in soft retreat temporaneo + /// + [JsonIgnore] + public bool IsInSoftRetreat { get; set; } = false; + + /// + /// Contatore puntate effettuate in questa sessione su questa asta + /// + [JsonIgnore] + public int SessionBidCount { get; set; } = 0; + + /// + /// Numero di volte che il timer è scaduto prima della puntata + /// + [JsonIgnore] + public int TimerExpiredCount { get; set; } = 0; + + /// + /// Numero di puntate riuscite + /// + [JsonIgnore] + public int SuccessfulBidCount { get; set; } = 0; + + /// + /// Numero di puntate fallite + /// + [JsonIgnore] + public int FailedBidCount { get; set; } = 0; + + /// + /// Lista utenti identificati come aggressivi in questa asta + /// + [JsonIgnore] + public HashSet AggressiveBidders { get; set; } = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Offset dinamico calcolato per questa asta (ms) + /// + [JsonIgnore] + public int DynamicOffsetMs { get; set; } = 150; + + /// + /// Offset effettivo usato nell'ultima puntata (include jitter) + /// + [JsonIgnore] + public int LastUsedOffsetMs { get; set; } = 0; + + /// + /// Indica se questa asta è stata seguita dall'inizio (per salvare storia completa) + /// + public bool IsTrackedFromStart { get; set; } = false; + + /// + /// Timestamp di inizio tracking + /// + public DateTime? TrackingStartedAt { get; set; } + + // ??????????????????????????????????????????????????????????????? + // IMPOSTAZIONI PER-ASTA (override globali) + // ??????????????????????????????????????????????????????????????? + + /// + /// Override: abilita/disabilita strategie avanzate per questa asta + /// null = usa impostazione globale + /// + public bool? AdvancedStrategiesEnabled { get; set; } + + /// + /// Override: abilita/disabilita jitter per questa asta + /// + public bool? JitterEnabledOverride { get; set; } + + /// + /// Override: abilita/disabilita soft retreat per questa asta + /// + public bool? SoftRetreatEnabledOverride { get; set; } + + /// + /// Override: limite puntate per questa asta + /// + public int? MaxBidsOverride { get; set; } + + // ?? NUOVO: Rilevamento situazione di duello + + /// + /// True se rilevata situazione di duello (solo 2 bidder dominanti) + /// + [JsonIgnore] + public bool IsDuelSituation { get; set; } = false; + + /// + /// Username dell'avversario in caso di duello + /// + [JsonIgnore] + public string? DuelOpponent { get; set; } + + /// + /// Vantaggio/svantaggio nel duello (% puntate mie - % puntate avversario) + /// Positivo = sto dominando, Negativo = sto perdendo + /// + [JsonIgnore] + public double DuelAdvantage { get; set; } = 0; } /// diff --git a/Mimante/Models/ProductStatisticsRecord.cs b/Mimante/Models/ProductStatisticsRecord.cs index 302ded8..95d91d3 100644 --- a/Mimante/Models/ProductStatisticsRecord.cs +++ b/Mimante/Models/ProductStatisticsRecord.cs @@ -117,4 +117,62 @@ namespace AutoBidder.Models public double WinRate => TotalAuctions > 0 ? (double)WonAuctions / TotalAuctions * 100 : 0; } + + /// + /// Record completo storia asta con tutte le metriche avanzate + /// + public class CompleteAuctionHistoryRecord + { + public int Id { get; set; } + public string AuctionId { get; set; } = ""; + public string AuctionName { get; set; } = ""; + public string? ProductKey { get; set; } + public string? OriginalUrl { get; set; } + + // Dati finali + public double FinalPrice { get; set; } + public double? BuyNowPrice { get; set; } + public double? ShippingCost { get; set; } + public double? TotalCost { get; set; } + public double? Savings { get; set; } + public double? SavingsPercentage { get; set; } + + // Risultato + public bool Won { get; set; } + public string? WinnerUsername { get; set; } + public int? WinnerBidsUsed { get; set; } + + // Metriche competizione + public int TotalResets { get; set; } + public int TotalUniqueBidders { get; set; } + public int MaxHeatMetric { get; set; } + public double AvgHeatMetric { get; set; } + public int TotalCollisions { get; set; } + + // Mie statistiche + public int MyBidsUsed { get; set; } + public int MySuccessfulBids { get; set; } + public int MyFailedBids { get; set; } + public int MyTimerExpired { get; set; } + public double? MyAvgLatencyMs { get; set; } + + // Timestamps + public DateTime ClosedAt { get; set; } + public int ClosedAtHour { get; set; } + public int? DurationSeconds { get; set; } + public bool IsCompleteTracking { get; set; } + + // JSON + public string? AggressiveBiddersJson { get; set; } + public string? BiddersSummaryJson { get; set; } + + // Proprietà calcolate + public string DurationFormatted => DurationSeconds.HasValue + ? TimeSpan.FromSeconds(DurationSeconds.Value).ToString(@"hh\:mm\:ss") + : "-"; + + public double SuccessRate => (MySuccessfulBids + MyFailedBids) > 0 + ? (double)MySuccessfulBids / (MySuccessfulBids + MyFailedBids) * 100 + : 0; + } } diff --git a/Mimante/NUOVO_WORKFLOW_RIEPILOGO.md b/Mimante/NUOVO_WORKFLOW_RIEPILOGO.md deleted file mode 100644 index 7e24e42..0000000 --- a/Mimante/NUOVO_WORKFLOW_RIEPILOGO.md +++ /dev/null @@ -1,250 +0,0 @@ -# ?? Nuovo Workflow Docker + Gitea - RIEPILOGO - -## ? Cosa è Cambiato - -### PRIMA (Approccio Custom) -- Profili `.pubxml` con comandi Docker custom -- Non compatibili con GUI Visual Studio -- Richiedeva comandi manuali da terminale - -### DOPO (Approccio Nativo Visual Studio) -- Profili `.pubxml` standard Docker di Visual Studio -- **Funziona dalla GUI** (Tasto destro ? Pubblica) -- Post-build target automatico nel `.csproj` -- Workflow completamente integrato - ---- - -## ?? Workflow Completo - -``` -??????????????????????????????????????????????????? -? Visual Studio ? Tasto Destro ? Pubblica ? -? Seleziona profilo: GiteaRegistry ? -??????????????????????????????????????????????????? - ? - ? -??????????????????????????????????????????????????? -? 1. Build .NET (Release) ? -??????????????????????????????????????????????????? - ? - ? -??????????????????????????????????????????????????? -? 2. Docker build ? -? docker build -t autobidder:latest . ? -??????????????????????????????????????????????????? - ? - ? -??????????????????????????????????????????????????? -? 3. POST-BUILD TARGET (AutoBidder.csproj) ? -? - Tag: autobidder:latest ? -? ? gitea.../alby96/autobidder:latest ? -? - Tag: autobidder:latest ? -? ? gitea.../alby96/autobidder:1.0.0 ? -??????????????????????????????????????????????????? - ? - ? -??????????????????????????????????????????????????? -? 4. Push su Gitea ? -? - docker push .../autobidder:latest ? -? - docker push .../autobidder:1.0.0 ? -??????????????????????????????????????????????????? - ? - ? -??????????????????????????????????????????????????? -? ? PUBBLICATO SU GITEA ? -? https://gitea.../Alby96/-/packages ? -??????????????????????????????????????????????????? -``` - ---- - -## ?? File Modificati - -### 1. `AutoBidder.csproj` - -**Aggiunto:** -```xml - - - - -``` - -### 2. `Properties/PublishProfiles/GiteaRegistry.pubxml` (NUOVO) - -```xml - - - Docker - true - DockerContainer - - autobidder:latest - true - - -``` - -**Cosa fa:** -- Build Docker dell'immagine locale -- Attiva post-build target per push su Gitea -- **Funziona da GUI Visual Studio** ? - -### 3. `Properties/PublishProfiles/GiteaRegistry-LocalOnly.pubxml` (NUOVO) - -```xml - - - Docker - true - DockerContainer - - autobidder:latest - false - - -``` - -**Cosa fa:** -- Build Docker solo locale -- NESSUN push su Gitea -- Utile per test - -### 4. `DOCKER_PUBLISH_GUIDE.md` (AGGIORNATA) - -- Istruzioni per uso da Visual Studio GUI -- Workflow completo documentato -- Troubleshooting aggiornato - ---- - -## ?? Come Usare - -### Opzione 1: Da Visual Studio (CONSIGLIATO) - -1. **Tasto destro** sul progetto `AutoBidder` -2. Click **Pubblica** -3. Seleziona profilo: **`GiteaRegistry`** -4. Click **Pubblica** - -? **FATTO!** L'immagine viene buildat?, taggata e pubblicata automaticamente. - -### Opzione 2: Da Riga di Comando - -```bash -dotnet publish -c Release /p:PublishProfile=GiteaRegistry -``` - -### Opzione 3: Solo Build Locale (Test) - -```bash -dotnet publish -c Release /p:PublishProfile=GiteaRegistry-LocalOnly -``` - ---- - -## ?? Prerequisito: Autenticazione - -**Prima volta (OBBLIGATORIO):** - -```bash -# 1. Genera Token PAT su Gitea -# https://gitea.encke-hake.ts.net/user/settings/applications -# Scope: read:packages + write:packages - -# 2. Autentica Docker -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [TOKEN PAT] -``` - -**NOTA:** Se hai 2FA su Gitea, il Token PAT è **OBBLIGATORIO**. - ---- - -## ? Vantaggi del Nuovo Approccio - -| Aspetto | Prima (Custom) | Dopo (Nativo VS) | -|---------|----------------|------------------| -| **GUI Visual Studio** | ? Non funzionava | ? Funziona perfettamente | -| **Semplicità** | Comandi manuali | Click ? Pubblica | -| **Standard** | Approccio custom | Standard Microsoft | -| **Manutenibilità** | Complesso | Semplice | -| **Errori** | Difficili da debuggare | Output chiaro | -| **Workflow** | Multi-step manuale | Automatico end-to-end | - ---- - -## ?? Verifica Post-Pubblicazione - -Dopo la pubblicazione, Visual Studio mostrerà: - -``` -======================================== -POST-BUILD: Tagging and pushing to Gitea Registry -======================================== -Tagged: gitea.encke-hake.ts.net/alby96/autobidder:latest -Tagged: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -======================================== -Pushing to Gitea Registry... -======================================== -Pushed: gitea.encke-hake.ts.net/alby96/autobidder:latest -Pushed: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -======================================== -SUCCESS: Images published to Gitea! -======================================== -View on Gitea: -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder/latest -======================================== -``` - -**Verifica su Gitea:** -- Vai su: `https://gitea.encke-hake.ts.net/Alby96/-/packages` -- Cerca package: `autobidder` (tipo: Container) -- Verifica tag: `latest` e `1.0.0` -- Controlla data: dovrebbe essere oggi - ---- - -## ?? Prossimi Passi - -1. ? Autenticati con Docker (Token PAT) -2. ? Prova pubblicazione: Tasto destro ? Pubblica ? GiteaRegistry -3. ? Verifica su Gitea che l'immagine sia caricata -4. ? Deploy su Unraid/altro server - ---- - -## ?? Note Importanti - -### Convenzione Nomi Gitea (CORRETTA) - -``` -gitea.encke-hake.ts.net/alby96/autobidder:latest -??????????????????????? ?????? ??????????? - registro owner immagine - -? 3 LIVELLI (corretto) -? Non usare: /alby96/mimante/autobidder (4 livelli - ERRATO) -``` - -### Post-Build Condition - -Il post-build target si attiva **SOLO** se: -- Profilo ha `true` -- `GiteaRegistry.pubxml` ? Push attivato ? -- `GiteaRegistry-LocalOnly.pubxml` ? Push disabilitato ? - -### Aggiornamento Versione - -Per pubblicare nuova versione: -1. Modifica `1.0.1` in `AutoBidder.csproj` -2. Pubblica normalmente -3. Vengono creati tag: `latest` (aggiornato) + `1.0.1` (nuovo) - ---- - -**? CONFIGURAZIONE COMPLETATA!** - -Ora hai un workflow professionale integrato con Visual Studio per pubblicare su Gitea! ?? diff --git a/Mimante/PROBLEMA_HTTPS_RISOLTO.md b/Mimante/PROBLEMA_HTTPS_RISOLTO.md deleted file mode 100644 index 6e88cfe..0000000 --- a/Mimante/PROBLEMA_HTTPS_RISOLTO.md +++ /dev/null @@ -1,274 +0,0 @@ -# ?? Problema HTTPS in Docker - RISOLTO - -## ? Errore Originale - -``` -Unhandled exception. System.InvalidOperationException: -Unable to configure HTTPS endpoint. No server certificate was specified, -and the default developer certificate could not be found or is out of date. -To generate a developer certificate run 'dotnet dev-certs https'. -To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'. - -at Program.<>c.<
$>b__0_6(ListenOptions listenOptions) in /src/Program.cs:line 17 -``` - -## ?? Analisi del Problema - -### Causa - -**Nel `Program.cs` (versione precedente):** - -```csharp -// PROBLEMA: In Development, enableHttps = true -var enableHttps = builder.Configuration.GetValue( - "Kestrel:EnableHttps", - builder.Environment.IsDevelopment() // ? true in Dev! -); - -builder.WebHost.ConfigureKestrel(options => -{ - options.ListenAnyIP(5000); // HTTP - - if (enableHttps) - { - options.ListenAnyIP(5001, listenOptions => - { - // ? Cerca certificato che non esiste in container! - listenOptions.UseHttps(); - }); - } -}); -``` - -**Problema:** -- In ambiente `Development` (o assente), `enableHttps = true` -- In Docker, `ASPNETCORE_ENVIRONMENT=Production` ma il certificato non esiste -- Kestrel fallisce all'avvio cercando certificati di sviluppo - -### Flusso Errore - -``` -1. Docker build ? ASPNETCORE_ENVIRONMENT=Production -2. Program.cs ? IsDevelopment() = false -3. Ma se Kestrel:EnableHttps non è settato ? usa default -4. In alcune configurazioni, tenta comunque HTTPS -5. listenOptions.UseHttps() ? cerca certificato -6. Certificato non trovato ? CRASH! ? -``` - ---- - -## ? Soluzione Implementata - -### 1. Modifica `Program.cs` - -```csharp -// ? CORRETTO: HTTPS disabilitato di default -var enableHttps = builder.Configuration.GetValue("Kestrel:EnableHttps", false); - -builder.WebHost.ConfigureKestrel(options => -{ - options.ListenAnyIP(8080); // HTTP porta standard container - - if (enableHttps) - { - try - { - // Cerca certificato esplicito da configurazione - var certPath = builder.Configuration["Kestrel:Certificates:Default:Path"]; - var certPassword = builder.Configuration["Kestrel:Certificates:Default:Password"]; - - if (!string.IsNullOrEmpty(certPath) && File.Exists(certPath)) - { - // Usa certificato fornito (production con cert) - options.ListenAnyIP(8443, listenOptions => - { - listenOptions.UseHttps(certPath, certPassword); - }); - } - else if (builder.Environment.IsDevelopment()) - { - // Certificato dev SOLO se esplicitamente Development - options.ListenAnyIP(5001, listenOptions => - { - listenOptions.UseHttps(); - }); - } - else - { - Console.WriteLine("[Kestrel] HTTPS requested but no certificate found"); - Console.WriteLine("[Kestrel] Running in HTTP-only mode"); - } - } - catch (Exception ex) - { - Console.WriteLine($"[Kestrel] Failed to enable HTTPS: {ex.Message}"); - Console.WriteLine("[Kestrel] Running in HTTP-only mode"); - } - } - else - { - Console.WriteLine("[Kestrel] HTTPS disabled - running in HTTP-only mode"); - Console.WriteLine("[Kestrel] Use a reverse proxy for SSL termination"); - } -}); -``` - -### 2. Modifica `Dockerfile` - -```dockerfile -# Environment variables -ENV ASPNETCORE_URLS=http://+:8080 -ENV ASPNETCORE_ENVIRONMENT=Production -ENV Kestrel__EnableHttps=false # ? Disabilita HTTPS esplicitamente -``` - -### 3. Porta Cambiata - -- ? Prima: `5000` (HTTP) + `5001` (HTTPS) -- ? Dopo: `8080` (HTTP standard container) - ---- - -## ?? Confronto Prima/Dopo - -| Aspetto | Prima | Dopo | -|---------|-------|------| -| **Default HTTPS** | ? Abilitato in Dev | ? Disabilitato | -| **Porta HTTP** | 5000 | 8080 (standard) | -| **Porta HTTPS** | 5001 (fallisce) | 8443 (opzionale) | -| **Certificato** | Richiesto | Opzionale | -| **Crash startup** | ? Sì | ? No | -| **Reverse proxy** | N/A | ? Consigliato | - ---- - -## ?? Best Practices per HTTPS in Container - -### ? NON FARE (Anti-pattern) - -```dockerfile -# ? SBAGLIATO: Abilita HTTPS senza certificato -ENV ASPNETCORE_URLS=https://+:5001 -``` - -### ? PATTERN CORRETTO - -**Opzione 1: HTTP Only + Reverse Proxy (CONSIGLIATO)** - -```dockerfile -# Container espone solo HTTP -ENV ASPNETCORE_URLS=http://+:8080 -ENV Kestrel__EnableHttps=false -``` - -```nginx -# Nginx gestisce SSL -server { - listen 443 ssl; - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_certificate_key /etc/nginx/ssl/key.pem; - - location / { - proxy_pass http://autobidder:8080; - } -} -``` - -**Opzione 2: HTTPS con Certificato nel Container** - -```bash -docker run -d \ - -e Kestrel__EnableHttps=true \ - -e Kestrel__Certificates__Default__Path=/certs/cert.pfx \ - -e Kestrel__Certificates__Default__Password=password \ - -v /host/certs:/certs \ - -p 443:8443 \ - autobidder:latest -``` - ---- - -## ?? Test Correzione - -### Prima (ERRORE) - -```bash -docker run -p 5000:5000 autobidder:latest -# System.InvalidOperationException: Unable to configure HTTPS endpoint -# ? Container CRASH! -``` - -### Dopo (SUCCESS) - -```bash -docker run -p 5000:8080 autobidder:latest -# [Kestrel] HTTPS disabled - running in HTTP-only mode -# [Kestrel] Use a reverse proxy for SSL termination -# ? Application started successfully! -``` - -### Verifica - -```bash -# Container in esecuzione -docker ps -# CONTAINER ID IMAGE PORTS -# abc123 autobidder:latest 0.0.0.0:5000->8080/tcp - -# Test endpoint -curl http://localhost:5000 -# ? Risposta OK! -``` - ---- - -## ?? Configurazione Unraid/Docker Compose - -### Docker Compose - -```yaml -version: '3.8' - -services: - autobidder: - image: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - container_name: autobidder - ports: - - "5000:8080" # Host:Container - volumes: - - ./data:/app/Data - - ./logs:/app/logs - environment: - - ASPNETCORE_ENVIRONMENT=Production - - Kestrel__EnableHttps=false - restart: unless-stopped -``` - -### Unraid Template - -``` -Repository: gitea.encke-hake.ts.net/alby96/autobidder:latest -Port: 5000 (host) ? 8080 (container) [HTTP] -Volume: /mnt/user/appdata/autobidder/data ? /app/Data -Volume: /mnt/user/appdata/autobidder/logs ? /app/logs -Environment: ASPNETCORE_ENVIRONMENT=Production -Environment: Kestrel__EnableHttps=false -``` - ---- - -## ? Checklist Finale - -- [x] HTTPS disabilitato di default in container -- [x] Porta HTTP cambiata da 5000 ? 8080 (standard) -- [x] Dockerfile aggiornato con `Kestrel__EnableHttps=false` -- [x] Program.cs modificato per gestire correttamente HTTPS opzionale -- [x] Certificati di sviluppo SOLO in ambiente Development -- [x] Reverse proxy consigliato per SSL in production -- [x] Documentazione aggiornata -- [x] Container si avvia senza errori - -**PROBLEMA RISOLTO!** ?? - -Container ora si avvia correttamente in modalità HTTP-only, pronto per reverse proxy SSL in production. diff --git a/Mimante/PROBLEMA_RISOLTO.md b/Mimante/PROBLEMA_RISOLTO.md deleted file mode 100644 index add9ea6..0000000 --- a/Mimante/PROBLEMA_RISOLTO.md +++ /dev/null @@ -1,214 +0,0 @@ -# ?? PROBLEMA RISOLTO: Errore Visual Studio con Push Riuscito - -## ?? Analisi del Problema - -### ? Cosa Funzionava - -Dal log di pubblicazione: -``` -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -docker push gitea.encke-hake.ts.net/alby96/autobidder:latest -... -latest: digest: sha256:dc08591c525e29d881f65effbc569a1c4c75d7d43614d75231e9c8035e3865b0 size: 856 -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:latest - -docker push gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -... -1.0.0: digest: sha256:dc08591c525e29d881f65effbc569a1c4c75d7d43614d75231e9c8035e3865b0 size: 856 -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -????????????????????????????????????????????????????????????????????? -? ? PUBBLICAZIONE COMPLETATA CON SUCCESSO! ? -????????????????????????????????????????????????????????????????????? -``` - -**Tutto perfetto**: Build, tag e push su Gitea funzionanti al 100%! - -### ? Errore Visual Studio - -Alla fine del processo: -``` -1>La compilazione non è riuscita. Vedere la finestra di output per altre informazioni. -========== Pubblicazione: 0 completato/i, 1 non riuscito/i, 0 ignorato/i ========== - -Errore MSB4057: la destinazione "ContainerBuild" non è presente nel progetto. -C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Sdks\Microsoft.Docker.Sdk\build\Microsoft.Docker.targets(173,5) -``` - ---- - -## ?? Causa del Problema - -### Configurazione Precedente (ERRATA) - -**File:** `Properties/PublishProfiles/GiteaRegistry.pubxml` - -```xml - - - - Docker - true - DockerContainer - <_TargetId>Docker - - autobidder:latest - true - - -``` - -**Problema:** -- `Docker` richiede **Microsoft.Docker.Sdk** -- Visual Studio cerca il target `ContainerBuild` nel progetto -- Il target non esiste perché l'SDK non è installato (e non serve!) -- Visual Studio fallisce DOPO che il nostro workflow custom ha già pubblicato con successo - -### Flusso Esecuzione - -``` -1. ? Build .NET (Release) -2. ? Publish files ? obj\Docker\publish -3. ? Post-build target "PushDockerImageToGitea" (dal .csproj) - ?? ? docker build - ?? ? docker tag - ?? ? docker push (SUCCESSO!) -4. ? Visual Studio cerca target "ContainerBuild" (Docker SDK) -5. ? Target non trovato ? ERRORE (ma push già fatto!) -``` - ---- - -## ? Soluzione Implementata - -### Nuova Configurazione (CORRETTA) - -**File:** `Properties/PublishProfiles/GiteaRegistry.pubxml` - -```xml - - - - Custom - FileSystem - Release - Any CPU - - - obj\Docker\publish - True - - - autobidder:latest - $(MSBuildProjectDirectory)\Dockerfile - $(MSBuildProjectDirectory) - - - true - - - - - - - - - -``` - -**Vantaggi:** -- ? Non richiede Microsoft.Docker.Sdk -- ? Visual Studio non cerca target mancanti -- ? Controllo completo del workflow -- ? Nessun errore alla fine del processo - -### Nuovo Flusso Esecuzione - -``` -1. ? Build .NET (Release) -2. ? Publish files ? obj\Docker\publish -3. ? Target "DockerBuild" (dal profilo .pubxml) - ?? docker build -t autobidder:latest -4. ? Post-build target "PushDockerImageToGitea" (dal .csproj) - ?? docker tag ? gitea.../autobidder:latest - ?? docker tag ? gitea.../autobidder:1.0.0 - ?? docker push latest - ?? docker push 1.0.0 -5. ? Visual Studio: SUCCESS! ? -``` - ---- - -## ?? Confronto Prima/Dopo - -| Aspetto | Prima (Docker SDK) | Dopo (Custom) | -|---------|-------------------|---------------| -| **WebPublishMethod** | `Docker` | `Custom` | -| **Richiede SDK** | ? Sì (non installato) | ? No | -| **Docker Build** | Post-build .csproj | Target .pubxml + Post-build | -| **Errore finale** | ? Sì (target mancante) | ? No | -| **Push funziona** | ? Sì | ? Sì | -| **Visual Studio OK** | ? No (errore) | ? Sì | - ---- - -## ?? Risultato Finale - -### Output Pubblicazione Corretta - -``` -????????????????????????????????????????????????????????????????????? -? DOCKER BUILD: Building container image ? -????????????????????????????????????????????????????????????????????? - -?? Building: autobidder:latest -? Docker build completed successfully! - -????????????????????????????????????????????????????????????????????? -? POST-BUILD: Pubblicazione su Gitea Container Registry ? -????????????????????????????????????????????????????????????????????? - -?? Solution Version: 1.0.0 -??? Target Tags: - • gitea.encke-hake.ts.net/alby96/autobidder:latest - • gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -????????????????????????????????????????????????????????????????????? -? ? PUBBLICAZIONE COMPLETATA CON SUCCESSO! ? -????????????????????????????????????????????????????????????????????? - -========== Pubblicazione: 1 completato/i, 0 non riuscito/i, 0 ignorato/i ========== -``` - -**Visual Studio mostra SUCCESS senza errori!** ? - ---- - -## ?? Lezioni Apprese - -1. **`WebPublishMethod=Docker`** richiede Microsoft.Docker.Sdk installato -2. **`WebPublishMethod=Custom`** permette workflow personalizzati senza SDK -3. Il nostro workflow custom funzionava già (push riuscito), ma Visual Studio non era soddisfatto -4. Separare build Docker (target nel .pubxml) da push Gitea (target nel .csproj) rende il processo più chiaro -5. Visual Studio può mostrare errori anche se l'operazione è riuscita (cerca target che non trova) - ---- - -## ? Checklist Verifica - -- [x] Build .NET funziona -- [x] Docker build funziona -- [x] Tag Gitea creati -- [x] Push su Gitea riuscito -- [x] Visual Studio non mostra errori -- [x] Digest SHA256 visibile su Gitea -- [x] Immagini disponibili per pull - -**TUTTO FUNZIONANTE!** ?? diff --git a/Mimante/Pages/Index.razor b/Mimante/Pages/Index.razor index be38020..7cb1641 100644 --- a/Mimante/Pages/Index.razor +++ b/Mimante/Pages/Index.razor @@ -48,13 +48,13 @@
- - - +
@@ -166,7 +169,7 @@ -
+
@if (globalLog.Count == 0) {
Nessun log ancora...
@@ -178,6 +181,7 @@
@logEntry.Message
} } +
@@ -255,21 +259,11 @@
-
- - +
+ + + Limite puntate per questa asta. 0 o vuoto = usa limite globale.
-
- - -
-
- -
- -
@@ -442,17 +436,27 @@ @{ // Crea una copia thread-safe per evitare modifiche durante l'enumerazione var recentBidsCopy = GetRecentBidsSafe(selectedAuction); + + // ?? FIX: Per l'utente corrente, usa BidsUsedOnThisAuction (valore ufficiale dal server) + var myOfficialBidsCount = selectedAuction.BidsUsedOnThisAuction ?? 0; + var currentUsername = GetCurrentUsername(); } @if (recentBidsCopy.Any()) { // Calcola statistiche puntatori var bidderStats = recentBidsCopy .GroupBy(b => b.Username) - .Select(g => new { Username = g.Key, Count = g.Count(), IsMe = g.First().IsMyBid }) + .Select(g => new { + Username = g.Key, + // Per l'utente corrente usa il conteggio ufficiale, per gli altri conta dalla lista + Count = g.First().IsMyBid && myOfficialBidsCount > 0 ? myOfficialBidsCount : g.Count(), + IsMe = g.First().IsMyBid + }) .OrderByDescending(s => s.Count) .ToList(); - var totalBids = recentBidsCopy.Count; + // Ricalcola il totale includendo il conteggio corretto per l'utente + var totalBids = bidderStats.Sum(b => b.Count);
diff --git a/Mimante/Pages/Index.razor.cs b/Mimante/Pages/Index.razor.cs index 4ace39e..2adf6a4 100644 --- a/Mimante/Pages/Index.razor.cs +++ b/Mimante/Pages/Index.razor.cs @@ -46,6 +46,10 @@ namespace AutoBidder.Pages private bool isLoadingRecommendations = false; private string? recommendationMessage = null; private bool recommendationSuccess = false; + + // Auto-scroll log + private ElementReference globalLogRef; + private int lastLogCount = 0; protected override void OnInitialized() { @@ -91,6 +95,17 @@ namespace AutoBidder.Pages await JSRuntime.InvokeVoidAsync("addDeleteKeyListener", DotNetObjectReference.Create(this)); } + + // Auto-scroll log globale quando ci sono nuovi messaggi + if (globalLog.Count != lastLogCount) + { + lastLogCount = globalLog.Count; + try + { + await JSRuntime.InvokeVoidAsync("scrollToBottom", "globalLogContainer"); + } + catch { /* Ignora errori JS */ } + } } // Handler async per eventi da background thread @@ -462,6 +477,12 @@ namespace AutoBidder.Pages // Decodifica HTML entities productName = System.Net.WebUtility.HtmlDecode(productName); + // ?? FIX: Sostituisci entità HTML non standard + productName = productName + .Replace("+", "+") + .Replace("&plus;", "+") + .Replace(" + ", " & "); // Normalizza separatori + if (!string.IsNullOrWhiteSpace(productName) && productName != auction.Name) { auction.Name = productName; @@ -562,6 +583,42 @@ namespace AutoBidder.Pages } } + private async Task RemoveAllAuctions() + { + if (auctions.Count == 0) return; + + var count = auctions.Count; + var confirmed = await JSRuntime.InvokeAsync("confirm", + $"Rimuovere TUTTE le {count} aste?\n\n" + + "?? Le aste terminate verranno salvate automaticamente nelle statistiche.\n" + + "Le aste non terminate andranno perse."); + + if (!confirmed) return; + + try + { + // Copia la lista per iterare in modo sicuro + var auctionsToRemove = auctions.ToList(); + + foreach (var auction in auctionsToRemove) + { + AuctionMonitor.RemoveAuction(auction.AuctionId); + AppState.RemoveAuction(auction); + } + + SaveAuctions(); + selectedAuction = null; + + AddLog($"[BULK] Rimosse {count} aste"); + await JSRuntime.InvokeVoidAsync("alert", $"? Rimosse {count} aste con successo"); + } + catch (Exception ex) + { + AddLog($"Errore rimozione bulk: {ex.Message}"); + await JSRuntime.InvokeVoidAsync("alert", $"Errore durante la rimozione:\n{ex.Message}"); + } + } + private async Task RemoveSelectedAuctionWithConfirm() { if (selectedAuction == null) return; @@ -711,7 +768,6 @@ namespace AutoBidder.Pages // Stati controllati dall'utente if (!auction.IsActive) return "Fermata"; if (auction.IsPaused) return "Pausa"; - if (auction.IsAttackInProgress) return "Puntando"; return "Attiva"; } @@ -741,7 +797,6 @@ namespace AutoBidder.Pages // Stati controllati dall'utente if (!auction.IsActive) return ""; if (auction.IsPaused) return ""; - if (auction.IsAttackInProgress) return ""; return ""; } @@ -849,6 +904,11 @@ namespace AutoBidder.Pages } } + private string GetCurrentUsername() + { + return sessionUsername ?? ""; + } + // ?? NUOVI METODI: Visualizzazione valori prodotto private string GetTotalCostDisplay(AuctionInfo? auction) diff --git a/Mimante/Pages/Settings.razor b/Mimante/Pages/Settings.razor index f64ded2..40beaa9 100644 --- a/Mimante/Pages/Settings.razor +++ b/Mimante/Pages/Settings.razor @@ -163,12 +163,6 @@ -
-
- - -
-
@@ -178,6 +172,255 @@
+ +
+

+ +

+
+
+
Timing & Latenza
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
Strategia Anti-AutoBid Bidoo
+
+ + Come funziona: Bidoo ha un sistema di auto-puntata integrato che si attiva a ~2 secondi. + Aspettando che il timer scenda sotto questa soglia, lasciamo che gli utenti con auto-puntata attiva + puntino prima di noi, risparmiando puntate. +
+
+
+
+ + +
+
+
+ + +
Punta solo quando timer < questo valore (default: 1.8s)
+
+
+
+ + +
+
+
+ +
Rilevamento Competizione
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
Soft Retreat
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
Puntata Probabilistica
+
+
+
+ + +
+
+
+ + +
+
+ + +
Profiling Avversari
+
+
+
+ + +
+
+
+ + +
Puntate minime per essere considerato aggressivo
+
+
+ + +
Analizza le ultime N puntate (default: 30)
+
+
+ + +
% puntate nella finestra per essere aggressivo
+
+
+ + +
Puntate da analizzare per rilevare duelli (2 bidder dominanti)
+
+
+ + +
+
+ +
Gestione Budget
+
+
+
+ + +
+
+
+ + +
0 = illimitato
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + @if (!string.IsNullOrEmpty(applyToAllMessage)) + { +
+ + @applyToAllMessage +
+ } +
+
+
+

@@ -334,7 +577,7 @@

Versione
- v1.0.0 + v1.3.0
@@ -418,6 +661,11 @@ private string usernameInput = ""; private string? connectionError; private bool isConnecting; +// Applica a tutte le aste +private bool isApplyingToAll = false; +private string? applyToAllMessage = null; +private bool applyToAllSuccess = false; + private AutoBidder.Utilities.AppSettings settings = new(); private System.Threading.Timer? updateTimer; @@ -437,6 +685,62 @@ private System.Threading.Timer? updateTimer; }, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); } + private async Task ApplyStrategiesToAllAuctions() + { + isApplyingToAll = true; + applyToAllMessage = null; + StateHasChanged(); + + try + { + // Prima salva le impostazioni + SaveSettings(); + + // Applica le impostazioni di default a tutte le aste + var auctions = AuctionMonitor.GetAuctions().ToList(); + int count = 0; + + foreach (var auction in auctions) + { + // Applica impostazioni predefinite + auction.BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs; + auction.MinPrice = settings.DefaultMinPrice; + auction.MaxPrice = settings.DefaultMaxPrice; + auction.MinResets = settings.DefaultMinResets; + auction.MaxResets = settings.DefaultMaxResets; + + // Resetta override per usare impostazioni globali + auction.AdvancedStrategiesEnabled = null; + auction.JitterEnabledOverride = null; + auction.SoftRetreatEnabledOverride = null; + auction.MaxBidsOverride = null; + + count++; + } + + // Salva le aste modificate + AutoBidder.Utilities.PersistenceManager.SaveAuctions(auctions); + + applyToAllSuccess = true; + applyToAllMessage = $"? Strategie applicate a {count} aste con successo!"; + } + catch (Exception ex) + { + applyToAllSuccess = false; + applyToAllMessage = $"Errore: {ex.Message}"; + } + finally + { + isApplyingToAll = false; + StateHasChanged(); + + // Rimuovi messaggio dopo 5 secondi + await Task.Delay(5000); + applyToAllMessage = null; + StateHasChanged(); + } + } + private void SyncStartupSelectionsFromSettings() { if (settings.RememberAuctionStates) diff --git a/Mimante/Pages/Statistics.razor b/Mimante/Pages/Statistics.razor index 29a7e97..9ed812f 100644 --- a/Mimante/Pages/Statistics.razor +++ b/Mimante/Pages/Statistics.razor @@ -2,8 +2,12 @@ @attribute [Microsoft.AspNetCore.Authorization.Authorize] @using AutoBidder.Models @using AutoBidder.Services +@using Microsoft.JSInterop @inject StatsService StatsService @inject DatabaseService DatabaseService +@inject IJSRuntime JSRuntime +@inject AuctionMonitor AuctionMonitor +@inject ApplicationStateService AppState Statistiche - AutoBidder @@ -55,17 +59,58 @@
-
- - Aste Terminate Recenti -
+
+
+ + Aste Terminate (@(filteredAuctions?.Count ?? 0)) +
+
+ + +
+
+
+
+ + + @if (!string.IsNullOrEmpty(filterName)) + { + + } +
+
+
+ +
+
+ Clicca intestazioni per ordinare +
+
+
+
- @if (recentAuctions == null || !recentAuctions.Any()) + @if (filteredAuctions == null || !filteredAuctions.Any()) {
-

Nessuna asta terminata salvata

+

+ @if (!string.IsNullOrEmpty(filterName) || !string.IsNullOrEmpty(filterWon)) + { + Nessun risultato per i filtri applicati + } + else + { + Nessuna asta terminata salvata + } +

} else @@ -74,30 +119,68 @@
- - - + + + - - + + + - @foreach (var auction in recentAuctions) + @foreach (var auction in filteredAuctions) { - - + + - + + @@ -198,17 +281,159 @@ + + + @if (selectedAuctionDetail != null) + { +
+
+
+
+
+ + Dettagli Asta: @selectedAuctionDetail.AuctionName +
+ +
+
+
+ +
+
Informazioni Base
+
NomePrezzoPuntate + Nome @GetSortIndicator("name") + + Prezzo @GetSortIndicator("price") + + Puntate @GetSortIndicator("bids") + VincitoreStatoData + Stato @GetSortIndicator("won") + + Heat @GetSortIndicator("resets") + + Data @GetSortIndicator("date") +
@auction.AuctionName
+ @TruncateName(auction.AuctionName, 30) + @if (auction.TotalResets > 0) + { +
@auction.TotalResets reset + } +
€@auction.FinalPrice.ToString("F2")@auction.BidsUsed + @auction.BidsUsed + @if (auction.WinnerBidsUsed.HasValue && auction.WinnerBidsUsed != auction.BidsUsed) + { + /@auction.WinnerBidsUsed + } + @(auction.WinnerUsername ?? "-") @if (auction.Won) { - ? Vinta + ? } else { - ? Persa + ? + } + + @if (auction.TotalResets.HasValue && auction.TotalResets > 0) + { + + @(auction.TotalResets / 10.0 * 100 > 100 ? 100 : auction.TotalResets / 10.0 * 100)% + + } + else + { + - } @FormatTimestamp(auction.Timestamp)
+ + + + + + + + + @if (selectedAuctionDetail.BuyNowPrice.HasValue) + { + + + + + } + @if (selectedAuctionDetail.Savings.HasValue) + { + + + + + } + + + + + + + + + + + + +
ID Asta:@selectedAuctionDetail.AuctionId
Prezzo Finale:€@selectedAuctionDetail.FinalPrice.ToString("F2")
Valore Prodotto:€@selectedAuctionDetail.BuyNowPrice.Value.ToString("F2")
Risparmio:€@selectedAuctionDetail.Savings.Value.ToString("F2")
Risultato: + @if (selectedAuctionDetail.Won) + { + ? VINTA + } + else + { + ? Persa + } +
Vincitore:@(selectedAuctionDetail.WinnerUsername ?? "-")
Data Chiusura:@FormatTimestamp(selectedAuctionDetail.Timestamp)
+
+ + +
+
Statistiche Puntate
+ + + + + + @if (selectedAuctionDetail.WinnerBidsUsed.HasValue) + { + + + + + } + @if (selectedAuctionDetail.TotalResets.HasValue) + { + + + + + } + @if (selectedAuctionDetail.ClosedAtHour.HasValue) + { + + + + + } +
Le mie puntate:@selectedAuctionDetail.BidsUsed
Puntate vincitore:@selectedAuctionDetail.WinnerBidsUsed
Reset totali:@selectedAuctionDetail.TotalResets
Ora chiusura:@selectedAuctionDetail.ClosedAtHour:00
+
+ + +
+
Analisi Costi
+ + @if (selectedAuctionDetail.ShippingCost.HasValue) + { + + + + + } + @if (selectedAuctionDetail.TotalCost.HasValue) + { + + + + + } + @{ + var bidCost = selectedAuctionDetail.BidsUsed * 0.15; + } + + + + + @if (selectedAuctionDetail.ProductKey != null) + { + + + + + } +
Spedizione:€@selectedAuctionDetail.ShippingCost.Value.ToString("F2")
Costo totale:€@selectedAuctionDetail.TotalCost.Value.ToString("F2")
Costo puntate (~):€@bidCost.ToString("F2")
Chiave prodotto:@selectedAuctionDetail.ProductKey
+
+
+ + + + + } } @code { private bool isLoading = true; private List? recentAuctions; +private List? filteredAuctions; private List? products; -[Inject] private AuctionMonitor AuctionMonitor { get; set; } = default!; -[Inject] private ApplicationStateService AppState { get; set; } = default!; -[Inject] private IJSRuntime JSRuntime { get; set; } = default!; +// Filtri e ordinamento +private string filterName = ""; +private string filterWon = ""; +private AuctionResultExtended? selectedAuctionDetail; protected override async Task OnInitializedAsync() { @@ -222,8 +447,9 @@ private List? products; try { - // Carica aste recenti (ultime 50) - recentAuctions = await DatabaseService.GetRecentAuctionResultsAsync(50); + // Carica aste recenti (ultime 100 per permettere filtri) + recentAuctions = await DatabaseService.GetRecentAuctionResultsAsync(100); + ApplyFilters(); // Carica prodotti con statistiche products = await DatabaseService.GetAllProductStatisticsAsync(); @@ -239,6 +465,103 @@ private List? products; } } + + private void ApplyFilters() + { + if (recentAuctions == null) + { + filteredAuctions = null; + return; + } + + var filtered = recentAuctions.AsEnumerable(); + + // Filtro per nome + if (!string.IsNullOrWhiteSpace(filterName)) + { + filtered = filtered.Where(a => + a.AuctionName.Contains(filterName, StringComparison.OrdinalIgnoreCase)); + } + + // Filtro per stato + if (filterWon == "won") + { + filtered = filtered.Where(a => a.Won); + } + else if (filterWon == "lost") + { + filtered = filtered.Where(a => !a.Won); + } + + // Ordinamento + filtered = sortColumn switch + { + "date" => sortDescending ? filtered.OrderByDescending(a => a.Timestamp) : filtered.OrderBy(a => a.Timestamp), + "price" => sortDescending ? filtered.OrderByDescending(a => a.FinalPrice) : filtered.OrderBy(a => a.FinalPrice), + "bids" => sortDescending ? filtered.OrderByDescending(a => a.BidsUsed) : filtered.OrderBy(a => a.BidsUsed), + "name" => sortDescending ? filtered.OrderByDescending(a => a.AuctionName) : filtered.OrderBy(a => a.AuctionName), + "won" => sortDescending ? filtered.OrderByDescending(a => a.Won) : filtered.OrderBy(a => a.Won), + "resets" => sortDescending ? filtered.OrderByDescending(a => a.TotalResets ?? 0) : filtered.OrderBy(a => a.TotalResets ?? 0), + _ => filtered.OrderByDescending(a => a.Timestamp) // date_desc default + }; + + filteredAuctions = filtered.ToList(); + } + + private void ClearNameFilter() + { + filterName = ""; + ApplyFilters(); + } + + private string sortColumn = "date"; + private bool sortDescending = true; + + private void SortBy(string column) + { + if (sortColumn == column) + { + // Toggle direzione se stessa colonna + sortDescending = !sortDescending; + } + else + { + // Nuova colonna, default discendente + sortColumn = column; + sortDescending = true; + } + ApplyFilters(); + } + + private MarkupString GetSortIndicator(string column) + { + if (sortColumn != column) + return new MarkupString(""); + + return sortDescending + ? new MarkupString("") + : new MarkupString(""); + } + + private void SelectAuction(AuctionResultExtended auction) + { + selectedAuctionDetail = auction; + } + + private string GetHeatBadgeClass(int heat) + { + if (heat < 30) return "bg-success"; + if (heat < 60) return "bg-warning text-dark"; + return "bg-danger"; + } + + private string TruncateName(string name, int maxLength) + { + if (string.IsNullOrEmpty(name)) return "-"; + if (name.Length <= maxLength) return name; + return name.Substring(0, maxLength) + "..."; + } + private string FormatTimestamp(string timestamp) { if (DateTime.TryParse(timestamp, out var dt)) @@ -288,3 +611,27 @@ private List? products; } } } + + diff --git a/Mimante/Program.cs b/Mimante/Program.cs index 63db6c3..7037f28 100644 --- a/Mimante/Program.cs +++ b/Mimante/Program.cs @@ -176,9 +176,11 @@ var htmlCacheService = new HtmlCacheService( maxRetries: 2 ); -var auctionMonitor = new AuctionMonitor(); +var bidStrategyService = new BidStrategyService(); +var auctionMonitor = new AuctionMonitor(bidStrategyService); htmlCacheService.OnLog += (msg) => Console.WriteLine(msg); +builder.Services.AddSingleton(bidStrategyService); builder.Services.AddSingleton(auctionMonitor); builder.Services.AddSingleton(htmlCacheService); builder.Services.AddSingleton(sp => new SessionService(auctionMonitor.GetApiClient())); diff --git a/Mimante/QUICKSTART_SECURITY.md b/Mimante/QUICKSTART_SECURITY.md deleted file mode 100644 index de959a8..0000000 --- a/Mimante/QUICKSTART_SECURITY.md +++ /dev/null @@ -1,211 +0,0 @@ -# ?? QUICK START - AutoBidder v1.2.0 con Autenticazione - -## ? Deploy Rapido (5 minuti) - -### Step 1: Configura Password Admin (30 secondi) - -```bash -# Copia template -cp .env.example .env - -# Modifica password admin -nano .env - -# Imposta: -ADMIN_PASSWORD=TuaPasswordSicura123! -``` - -**Nota:** Le credenziali Bidoo NON servono! Il cookie di sessione si configura dall'interfaccia web. - -### Step 2: Pubblica Immagine (2 minuti) - -**Visual Studio:** -- Tasto destro progetto ? **Pubblica** -- Seleziona: **GiteaRegistry** -- Click **Pubblica** - -**Oppure CLI:** -```bash -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -### Step 3: Deploy su Unraid (2 minuti) - -``` -1. Docker ? Add Container -2. Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 -3. Port: 8889 (host) ? 8080 (container) -4. Volume: /mnt/user/appdata/autobidder/data ? /app/Data - -5. Environment Variables: - ADMIN_USERNAME=admin - ADMIN_PASSWORD=TuaPasswordSicura123! - -6. Apply ? Start -``` - -### Step 4: Primo Login (30 secondi) - -``` -1. Browser: http://192.168.30.23:8889 -2. Redirect automatico a /login -3. Username: admin -4. Password: TuaPasswordSicura123! -5. Click "Accedi" -6. ? Homepage AutoBidder! -``` - -### Step 5: Configura Sessione Bidoo (1 minuto) - -**Dopo il primo login:** - -1. Vai su **Settings** ? **Sessione Bidoo** -2. Incolla il cookie di sessione ottenuto da Bidoo.it -3. Salva - -**Come ottenere il cookie Bidoo:** -- Browser ? Bidoo.it ? Login -- F12 ? Application ? Cookies -- Copia valore cookie di sessione - ---- - -## ?? Credenziali Richieste - -### 1. Autenticazione Applicazione (SOLO AutoBidder) - -``` -ADMIN_USERNAME=admin -ADMIN_PASSWORD=MyS3cur3P@ss!2024 -``` - -**Requisiti password:** -- ? Min 12 caratteri -- ? Maiuscole + minuscole -- ? Numeri -- ? Simboli - -### 2. Sessione Bidoo (Configurata dall'interfaccia web) - -**NON servono credenziali qui!** - -Il cookie di sessione Bidoo si incolla manualmente dall'interfaccia: -- Login su AutoBidder -- Settings ? Sessione Bidoo -- Incolla cookie - ---- - -## ?? Credenziali Default (Se non configuri ADMIN_PASSWORD) - -**?? SOLO PER TEST LOCALE!** - -**Autenticazione app:** -``` -Username: admin -Password: Admin@Password123! -``` - -**?? Container mostrerà WARNING:** -``` -[Identity] WARNING: ADMIN_PASSWORD not set! Using default password. -[Identity] CHANGE IT IMMEDIATELY after first login! -[Bidoo] ERROR: BIDOO_USERNAME or BIDOO_PASSWORD not configured! -``` - ---- - -## ? Verifica Installazione - -```bash -# 1. Controlla log -docker logs AutoBidder | grep "\[Identity\]" - -# Output atteso: -[Identity] Database initialized -[Identity] Admin user created: admin - -# 2. Test login -curl -I http://192.168.30.23:8889 - -# Output atteso: -HTTP/1.1 302 Found -Location: /login - -# 3. Test dopo login -# Browser ? Homepage deve essere accessibile ? -``` - ---- - -## ?? Troubleshooting Rapido - -### Problema: "Account temporaneamente bloccato" - -``` -Causa: 5 tentativi falliti -Soluzione: Aspetta 15 minuti -``` - -### Problema: Pagina non carica - -```bash -# Verifica porta container -docker logs AutoBidder | grep "listening" -# Deve mostrare: Now listening on: http://[::]:8080 - -# Verifica port mapping -docker port AutoBidder -# Deve mostrare: 8080/tcp -> 0.0.0.0:8889 -``` - -### Problema: Password non accettata - -``` -Requisiti: -? Min 12 caratteri -? Maiuscola -? Minuscola -? Numero -? Simbolo - -Esempio valido: MyS3cur3P@ss!2024 -``` - ---- - -## ?? Deploy Production Checklist - -- [ ] Password forte configurata in `.env` -- [ ] `.env` NOT committed to git -- [ ] Immagine pubblicata su Gitea (`v1.2.0`) -- [ ] Container started con env vars corrette -- [ ] Primo login effettuato -- [ ] Tailscale ACL configurato (opzionale) -- [ ] Backup volume `/app/Data` configurato - ---- - -## ?? Aiuto - -**Log completi:** -```bash -docker logs AutoBidder --tail 100 -``` - -**Documentazione:** -- [SECURITY.md](SECURITY.md) - Guida completa sicurezza -- [CHANGELOG.md](CHANGELOG.md) - Note versione v1.2.0 -- [README.md](README.md) - Overview progetto - -**Reset completo (se necessario):** -```bash -docker stop AutoBidder -docker rm AutoBidder -# Riconfigura password in .env -docker run -d ... (comandi step 3) -``` - ---- - -**?? AutoBidder v1.2.0 - Pronto per produzione con sicurezza Tailscale!** diff --git a/Mimante/RELEASE_v1.1.1.md b/Mimante/RELEASE_v1.1.1.md deleted file mode 100644 index c3b5256..0000000 --- a/Mimante/RELEASE_v1.1.1.md +++ /dev/null @@ -1,280 +0,0 @@ -# ? RELEASE v1.1.1 - Fix Porta Container - -## ?? Bug Fix Critico - -**Versione:** `1.1.0` ? **`1.1.1`** -**Tipo:** PATCH (bug fix) -**Data:** 2025-01-18 - ---- - -## ? Problema Riscontrato - -### Sintomi -- ? Container si avvia senza errori -- ? Log mostra "Application started" -- ? Pagina web non carica -- ? Browser timeout o "connection refused" - -### Diagnosi - -**Log container:** -``` -info: Microsoft.Hosting.Lifetime[14] - Now listening on: http://[::]:5000 ? SBAGLIATO! -``` - -**Configurazione attesa:** -```dockerfile -ENV ASPNETCORE_URLS=http://+:8080 -EXPOSE 8080 -``` - -**Port mapping:** -```yaml -ports: - - "5000:8080" # Host ? Container -``` - -**Problema:** Container ascolta su 5000, ma port mapping cerca 8080 ? **MISMATCH!** - ---- - -## ? Soluzione Applicata - -### Modifica `Program.cs` - -**Prima (ERRATO):** -```csharp -builder.WebHost.ConfigureKestrel(options => -{ - options.ListenAnyIP(8080); // ? Ignorato! - // ... -}); -``` - -**Dopo (CORRETTO):** -```csharp -// NO configurazione esplicita HTTP -// ASPNETCORE_URLS gestisce tutto - -if (enableHttps) -{ - // Solo configurazione HTTPS opzionale - builder.WebHost.ConfigureKestrel(options => - { - // Porta 8443 per HTTPS - }); -} -else -{ - // Log porta HTTP da ASPNETCORE_URLS - Console.WriteLine($"[Kestrel] Listening on: {ASPNETCORE_URLS}"); -} -``` - -### Risultato - -**Log corretto:** -``` -[Kestrel] HTTPS disabled - running in HTTP-only mode -[Kestrel] Listening on: http://+:8080 -info: Microsoft.Hosting.Lifetime[14] - Now listening on: http://[::]:8080 ? CORRETTO! -``` - ---- - -## ?? Come Testare - -### 1. Rebuild Container - -```bash -# Stop container vecchio -docker stop autobidder -docker rm autobidder - -# Pull versione 1.1.1 -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.1.1 - -# Oppure build locale -docker build -t autobidder:1.1.1 . -``` - -### 2. Avvia Container - -```bash -docker run -d \ - --name autobidder \ - -p 5000:8080 \ - -v /data:/app/Data \ - gitea.encke-hake.ts.net/alby96/autobidder:1.1.1 -``` - -### 3. Verifica Log - -```bash -docker logs autobidder | grep "Now listening" - -# Output atteso: -# Now listening on: http://[::]:8080 ? -``` - -### 4. Test Accesso - -```bash -# Apri browser -http://localhost:5000 - -# Dovrebbe caricare la homepage AutoBidder ? -``` - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| `Program.cs` | Rimossa configurazione esplicita porta HTTP | Fix conflitto Kestrel | -| `AutoBidder.csproj` | Versione `1.1.1` | Incremento PATCH | -| `Dockerfile` | Label version `1.1.1` | Metadata immagine | -| `CHANGELOG.md` | Entry v1.1.1 | Documentazione fix | -| `FIX_PORTA_CONTAINER.md` | Nuovo documento | Troubleshooting dettagliato | - ---- - -## ?? Migrazione da v1.1.0 - -**Nessuna breaking change!** - -Aggiornamento semplice: - -```bash -# Docker -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.1.1 -docker-compose up -d - -# Unraid -# Cambia tag immagine: latest ? 1.1.1 -# Restart container -``` - ---- - -## ?? Documentazione - -### Nuovi Documenti - -- **`FIX_PORTA_CONTAINER.md`** - Troubleshooting dettagliato problema porta - - Diagnosi completa - - Soluzione passo-passo - - Test e verifica - - Override porta avanzato - -### Documenti Aggiornati - -- `CHANGELOG.md` - Entry v1.1.1 -- `README.md` - Badge versione aggiornato - ---- - -## ?? Benefici Fix - -### Prima (v1.1.0) -- ? Container parte ma pagina non carica -- ? Port mismatch difficile da diagnosticare -- ? Configurazione confusa -- ? Conflitti Kestrel vs ASPNETCORE_URLS - -### Dopo (v1.1.1) -- ? Container accessibile immediatamente -- ? Porta configurata centralmente (ASPNETCORE_URLS) -- ? Log chiaro della porta in ascolto -- ? Nessun conflitto configurazione -- ? Più facile override porta - ---- - -## ? Checklist Completata - -- [x] Problema identificato (porta 5000 vs 8080) -- [x] Root cause trovata (conflitto configurazione) -- [x] Fix applicato (rimossa config esplicita) -- [x] Build testata -- [x] Versione incrementata (1.1.1) -- [x] CHANGELOG aggiornato -- [x] Documentazione creata -- [x] Immagine pronta per pubblicazione - ---- - -## ?? Prossimi Passi - -### Pubblica su Gitea - -```bash -# Da Visual Studio -# Tasto destro ? Pubblica ? GiteaRegistry - -# Oppure CLI -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -### Commit e Tag - -```bash -git add . -git commit -m "fix: container listening on wrong port (5000 instead of 8080) - -- Remove explicit HTTP configuration from Kestrel -- Let ASPNETCORE_URLS control HTTP port -- Kestrel config now only for optional HTTPS -- Fixes web page not loading when accessing container - -Resolves #XX" - -git tag v1.1.1 -git push origin docker --tags -``` - ---- - -## ?? Metriche Fix - -- **Tempo diagnosi:** ~10 minuti -- **Tempo fix:** ~5 minuti -- **Righe modificate:** ~30 righe -- **File modificati:** 5 file -- **Documentazione:** 1 nuovo doc + aggiornamenti -- **Impatto:** **CRITICO** (container inaccessibile) -- **Difficoltà:** **BASSA** (una volta identificato) - ---- - -## ?? Lezioni Apprese - -1. **Configurazione esplicita vs variabili ambiente** - - Configurazione esplicita ha precedenza - - Può causare conflitti difficili da debuggare - - Meglio centralizzare config in env vars - -2. **Verifica sempre i log** - - "Now listening on:" mostra porta EFFETTIVA - - Può essere diversa da quella configurata - - Non fidarsi solo della configurazione - -3. **Port mapping deve corrispondere** - - Verifica porta container vs port mapping - - Usa `docker port ` per verificare - - Test endpoint prima di troubleshooting complesso - -4. **Keep It Simple** - - Meno configurazione = meno problemi - - ASPNETCORE_URLS è il modo standard - - ConfigureKestrel solo per casi speciali - ---- - -**? v1.1.1 PRONTO - Fix Critico Applicato!** - -Container ora accessibile correttamente sulla porta 8080! ?? diff --git a/Mimante/RIEPILOGO_COMPLETO_FINALE.md b/Mimante/RIEPILOGO_COMPLETO_FINALE.md deleted file mode 100644 index 0196441..0000000 --- a/Mimante/RIEPILOGO_COMPLETO_FINALE.md +++ /dev/null @@ -1,289 +0,0 @@ -# ? RIEPILOGO COMPLETO - CONFIGURAZIONE DOCKER + GITEA - -## ?? Problemi Risolti - -### 1. ? Convenzione Nomi Registry Gitea -**Problema:** Path errato con 4 livelli invece di 3 -- ? Prima: `gitea.../alby96/mimante/autobidder` -- ? Dopo: `gitea.../alby96/autobidder` - -### 2. ? Errore Visual Studio "ContainerBuild" -**Problema:** Profilo usava `WebPublishMethod=Docker` senza SDK -- ? Prima: Richiede Microsoft.Docker.Sdk -- ? Dopo: `WebPublishMethod=Custom` senza dipendenze - -### 3. ? Container HTTPS Crash -**Problema:** Kestrel cerca certificati HTTPS inesistenti -- ? Prima: HTTPS abilitato di default, crash all'avvio -- ? Dopo: HTTP only (8080), HTTPS opzionale - ---- - -## ?? File Modificati - -| File | Modifica | Motivo | -|------|----------|--------| -| `AutoBidder.csproj` | `` corretto | Convenzione Gitea 3 livelli | -| `AutoBidder.csproj` | Post-build target aggiunto | Push automatico su Gitea | -| `GiteaRegistry.pubxml` | `WebPublishMethod=Custom` | Nessuna dipendenza SDK Docker | -| `GiteaRegistry.pubxml` | Target `DockerBuild` | Build Docker integrato | -| `Program.cs` | `enableHttps=false` default | HTTPS disabilitato in container | -| `Program.cs` | Porta `8080` | Standard container HTTP | -| `Dockerfile` | `ENV Kestrel__EnableHttps=false` | Conferma HTTP only | -| `Dockerfile` | `EXPOSE 8080` | Porta HTTP standard | -| `docker-compose.yml` | `5000:8080` port mapping | Host:Container corretto | - ---- - -## ?? Workflow Finale - -### Da Visual Studio (1 Click) - -``` -1. Tasto destro progetto ? Pubblica ? GiteaRegistry -2. Visual Studio: - ?? Build .NET (Release) - ?? Target DockerBuild (profilo) ? docker build - ?? Post-build Gitea (csproj) ? tag + push - ?? ? SUCCESS! -``` - -### Output Completo - -``` -????????????????????????????????????????????????????????????????????? -? DOCKER BUILD: Building container image ? -????????????????????????????????????????????????????????????????????? - -?? Building: autobidder:latest -? Docker build completed successfully! - -????????????????????????????????????????????????????????????????????? -? POST-BUILD: Pubblicazione su Gitea Container Registry ? -????????????????????????????????????????????????????????????????????? - -?? Solution Version: 1.0.0 -??? Target Tags: - • gitea.encke-hake.ts.net/alby96/autobidder:latest - • gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Tagged: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:latest -? Pushed: gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 - -????????????????????????????????????????????????????????????????????? -? ? PUBBLICAZIONE COMPLETATA CON SUCCESSO! ? -????????????????????????????????????????????????????????????????????? -``` - ---- - -## ?? Configurazione Container - -### Porte - -| Ambiente | Host | Container | Protocollo | -|----------|------|-----------|------------| -| **Development** | 5001 | 5001 | HTTPS (dev cert) | -| **Docker/Production** | 5000 | 8080 | HTTP | -| **HTTPS Production** | 443 | 8443 | HTTPS (con cert) | - -### Variabili Ambiente - -```bash -# Container standard (HTTP only) -ASPNETCORE_URLS=http://+:8080 -ASPNETCORE_ENVIRONMENT=Production -Kestrel__EnableHttps=false - -# Con HTTPS (opzionale, richiede certificato) -Kestrel__EnableHttps=true -Kestrel__Certificates__Default__Path=/certs/cert.pfx -Kestrel__Certificates__Default__Password=password -``` - ---- - -## ?? Deploy su Gitea - -### Immagini Pubblicate - -``` -gitea.encke-hake.ts.net/alby96/autobidder:latest -gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -``` - -**Link Gitea:** -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder -``` - -### Versionamento Automatico - -```xml - -1.0.1 ? Incrementa qui -``` - -Pubblica ? Crea automaticamente: -- Tag `latest` (aggiornato) -- Tag `1.0.1` (nuovo) - ---- - -## ?? Comandi Rapidi - -### Autenticazione Gitea - -```bash -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [TOKEN PAT] -``` - -### Build Locale + Test - -```bash -# Build -docker build -t autobidder:test . - -# Test locale -docker run -p 5000:8080 \ - -v $(pwd)/Data:/app/Data \ - autobidder:test - -# Apri: http://localhost:5000 -``` - -### Pull da Gitea - -```bash -# Latest -docker pull gitea.encke-hake.ts.net/alby96/autobidder:latest - -# Versione specifica (production) -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -``` - -### Deploy Production - -```bash -docker run -d \ - --name autobidder \ - -p 5000:8080 \ - -v /data/autobidder:/app/Data \ - -v /logs/autobidder:/app/logs \ - -e ASPNETCORE_ENVIRONMENT=Production \ - --restart unless-stopped \ - gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -``` - -### Docker Compose - -```bash -# Start -docker-compose up -d - -# Logs -docker-compose logs -f autobidder - -# Stop -docker-compose down - -# Rebuild -docker-compose up -d --build -``` - ---- - -## ?? Reverse Proxy (HTTPS in Production) - -### Nginx - -```nginx -server { - listen 443 ssl http2; - server_name autobidder.example.com; - - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_certificate_key /etc/nginx/ssl/key.pem; - - location / { - proxy_pass http://autobidder:8080; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - -### Traefik - -```yaml -services: - autobidder: - image: gitea.encke-hake.ts.net/alby96/autobidder:latest - labels: - - "traefik.enable=true" - - "traefik.http.routers.autobidder.rule=Host(`autobidder.example.com`)" - - "traefik.http.routers.autobidder.tls=true" - - "traefik.http.routers.autobidder.tls.certresolver=letsencrypt" - - "traefik.http.services.autobidder.loadbalancer.server.port=8080" -``` - ---- - -## ?? Checklist Finale - -### Configurazione - -- [x] Convenzione Gitea corretta (3 livelli) -- [x] Versionamento automatico da `.csproj` -- [x] HTTPS disabilitato in container -- [x] Porta HTTP 8080 (standard) -- [x] Post-build push automatico -- [x] Profilo Visual Studio senza errori - -### Pubblicazione - -- [x] Build locale funziona -- [x] Docker build funziona -- [x] Tag Gitea creati (`latest` + versione) -- [x] Push su Gitea riuscito -- [x] Immagini visibili su Gitea -- [x] Visual Studio SUCCESS - -### Container - -- [x] Container si avvia senza errori -- [x] HTTP accessibile su porta 8080 -- [x] Volumi persistenti configurati -- [x] Healthcheck funzionante -- [x] Logs visibili - -### Documentazione - -- [x] DOCKER_PUBLISH_GUIDE.md completa -- [x] PROBLEMA_RISOLTO.md (Visual Studio) -- [x] PROBLEMA_HTTPS_RISOLTO.md (Container) -- [x] CONFIGURAZIONE_FINALE.md -- [x] NUOVO_WORKFLOW_RIEPILOGO.md -- [x] Questo riepilogo - ---- - -## ?? STATO: TUTTO FUNZIONANTE! - -**Workflow completo e testato:** -1. ? Modifica codice -2. ? Incrementa versione in `.csproj` -3. ? Pubblica da Visual Studio (1 click) -4. ? Immagini su Gitea (latest + versione) -5. ? Deploy su Unraid/Docker - -**Nessun errore, tutto automatico, versionamento tracciato!** ?? diff --git a/Mimante/RIEPILOGO_RELEASE_v1.1.0.md b/Mimante/RIEPILOGO_RELEASE_v1.1.0.md deleted file mode 100644 index 5f9d4b6..0000000 --- a/Mimante/RIEPILOGO_RELEASE_v1.1.0.md +++ /dev/null @@ -1,376 +0,0 @@ -# ?? RIEPILOGO FINALE - RELEASE v1.1.0 - -## ? Lavoro Completato - -### ?? Versione Rilasciata - -**Versione:** `1.1.0` (da `1.0.0`) -**Tipo:** MINOR (nuove feature + bug fix) -**Data:** 2025-01-18 - ---- - -## ?? File Creati (13 nuovi) - -### Documentazione - -1. **`README.md`** - Homepage progetto con badge e quick start -2. **`CHANGELOG.md`** - Storico completo modifiche (format standard) -3. **`VERSIONING.md`** - Guida sistema versionamento -4. **`VERSIONING_IMPLEMENTATO.md`** - Riepilogo implementazione -5. **`DOCKER_PUBLISH_GUIDE.md`** - Guida pubblicazione Gitea -6. **`CONFIGURAZIONE_FINALE.md`** - Riepilogo configurazione -7. **`NUOVO_WORKFLOW_RIEPILOGO.md`** - Dettagli workflow -8. **`VERIFICA_CONFIGURAZIONE_GITEA.md`** - Checklist conformità -9. **`PROBLEMA_RISOLTO.md`** - Fix errore Visual Studio -10. **`PROBLEMA_HTTPS_RISOLTO.md`** - Fix crash container -11. **`RIEPILOGO_COMPLETO_FINALE.md`** - Overview tutti i problemi - -### Profili e Script - -12. **`Properties/PublishProfiles/GiteaRegistry.pubxml`** - Profilo pubblicazione Gitea -13. **`bump-version.ps1`** - Script PowerShell per incremento versione automatico - ---- - -## ?? File Modificati (4) - -1. **`AutoBidder.csproj`** - - Versione aggiornata a `1.1.0` - - Post-build target per push Gitea - - Convenzione registry corretta - -2. **`Program.cs`** - - HTTPS disabilitato di default - - Porta HTTP: `8080` - - Gestione certificati migliorata - -3. **`Dockerfile`** - - Versione label aggiornata - - `ENV Kestrel__EnableHttps=false` - - Source URL corretto - -4. **`docker-compose.yml`** - - Port mapping aggiornato `5000:8080` - - Convenzione registry corretta - ---- - -## ? Funzionalità Implementate - -### 1. ?? Pubblicazione Automatica su Gitea - -**Workflow completo Visual Studio:** -``` -Tasto destro ? Pubblica ? GiteaRegistry - ? -Build .NET (Release) - ? -Docker build (autobidder:latest) - ? -Tag Gitea (latest + versione) - ? -Push automatico - ? -? SUCCESS! -``` - -**Output:** -- `gitea.encke-hake.ts.net/alby96/autobidder:latest` -- `gitea.encke-hake.ts.net/alby96/autobidder:1.1.0` - -### 2. ?? Sistema Versionamento Automatico - -**Semantic Versioning implementato:** -- MAJOR: Breaking changes (`1.x.x` ? `2.0.0`) -- MINOR: Nuove feature (`1.0.x` ? `1.1.0`) -- PATCH: Bug fix (`1.0.0` ? `1.0.1`) - -**Strumenti:** -- `bump-version.ps1` - Script automatico incremento -- `CHANGELOG.md` - Storico modifiche -- `VERSIONING.md` - Guida completa - -### 3. ?? Fix Container HTTPS - -**Problema:** -``` -System.InvalidOperationException: Unable to configure HTTPS endpoint -``` - -**Soluzione:** -- HTTPS disabilitato di default (`Kestrel__EnableHttps=false`) -- Porta HTTP standard: `8080` -- SSL gestito da reverse proxy - -### 4. ?? Fix Visual Studio - -**Problema:** -``` -Errore MSB4057: target "ContainerBuild" non presente -``` - -**Soluzione:** -- Profilo `Custom` senza dipendenze Docker SDK -- Target `DockerBuild` integrato -- Workflow senza errori - -### 5. ? Convenzione Gitea Corretta - -**Prima (ERRATO):** -``` -gitea.../alby96/mimante/autobidder (4 livelli) -``` - -**Dopo (CORRETTO):** -``` -gitea.../alby96/autobidder (3 livelli) -``` - ---- - -## ?? Modifiche Breaking - -### 1. Porta Container - -**Prima:** -```bash -docker run -p 5000:5000 ... -``` - -**Dopo:** -```bash -docker run -p 5000:8080 ... -``` - -### 2. HTTPS - -**Prima:** -- HTTPS abilitato di default -- Richiede certificati - -**Dopo:** -- HTTP only di default -- HTTPS opzionale con certificato - -### 3. Path Gitea - -**Prima:** -``` -gitea.../alby96/mimante/autobidder:latest -``` - -**Dopo:** -``` -gitea.../alby96/autobidder:latest -``` - ---- - -## ?? Come Usare - -### Incremento Versione Automatico - -```powershell -# Bug fix -.\bump-version.ps1 -Type patch # 1.1.0 ? 1.1.1 - -# Nuova feature -.\bump-version.ps1 -Type minor # 1.1.0 ? 1.2.0 - -# Breaking change -.\bump-version.ps1 -Type major # 1.1.0 ? 2.0.0 -``` - -### Pubblicazione su Gitea - -**Da Visual Studio:** -1. Tasto destro progetto ? **Pubblica** -2. Seleziona: **`GiteaRegistry`** -3. Click **Pubblica** - -**Da CLI:** -```bash -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -### Deploy Production - -```bash -# Pull versione specifica -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 - -# Avvia container -docker run -d \ - --name autobidder \ - -p 5000:8080 \ - -v /data:/app/Data \ - gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 -``` - ---- - -## ?? Documentazione Disponibile - -### Guide Utente - -| Documento | Scopo | -|-----------|-------| -| `README.md` | Homepage progetto, quick start, overview | -| `CHANGELOG.md` | Storico modifiche per versione | -| `DOCKER_PUBLISH_GUIDE.md` | Guida pubblicazione Gitea step-by-step | - -### Guide Sviluppatore - -| Documento | Scopo | -|-----------|-------| -| `VERSIONING.md` | Sistema versionamento, workflow release | -| `CONFIGURAZIONE_FINALE.md` | Riepilogo configurazione Docker/Gitea | -| `NUOVO_WORKFLOW_RIEPILOGO.md` | Dettagli tecnici workflow pubblicazione | - -### Troubleshooting - -| Documento | Scopo | -|-----------|-------| -| `PROBLEMA_RISOLTO.md` | Fix errore Visual Studio | -| `PROBLEMA_HTTPS_RISOLTO.md` | Fix crash container HTTPS | -| `VERIFICA_CONFIGURAZIONE_GITEA.md` | Checklist conformità | - -### Riepilogo - -| Documento | Scopo | -|-----------|-------| -| `RIEPILOGO_COMPLETO_FINALE.md` | Overview completa tutti i problemi | -| `VERSIONING_IMPLEMENTATO.md` | Dettagli implementazione versioning | -| **`RIEPILOGO_RELEASE_v1.1.0.md`** | **Questo documento** | - ---- - -## ? Checklist Completata - -### Configurazione - -- [x] Convenzione Gitea corretta (3 livelli) -- [x] Versionamento automatico da `.csproj` -- [x] HTTPS disabilitato in container -- [x] Porta HTTP 8080 (standard) -- [x] Post-build push automatico -- [x] Profilo Visual Studio funzionante - -### Pubblicazione - -- [x] Build locale testata -- [x] Docker build testato -- [x] Tag Gitea creati (`latest` + `1.1.0`) -- [x] Push su Gitea riuscito -- [x] Immagini visibili su Gitea -- [x] Visual Studio SUCCESS - -### Container - -- [x] Container si avvia senza errori -- [x] HTTP accessibile su porta 8080 -- [x] Volumi persistenti configurati -- [x] Healthcheck funzionante -- [x] Logs visibili - -### Documentazione - -- [x] README.md completo -- [x] CHANGELOG.md con v1.1.0 -- [x] VERSIONING.md con guida -- [x] Guide troubleshooting complete -- [x] Script automazione versione -- [x] Tutti i documenti aggiornati - ---- - -## ?? Prossimi Passi - -### Immediati - -1. **Commit modifiche:** - ```bash - git add . - git commit -m "chore: release v1.1.0 - - - Feature: Gitea publishing workflow - - Feature: Automatic versioning system - - Fix: Visual Studio ContainerBuild error - - Fix: Container HTTPS crash - - Docs: Complete documentation suite" - ``` - -2. **Tag release:** - ```bash - git tag v1.1.0 - git push origin docker --tags - ``` - -3. **Verifica pubblicazione:** - ``` - https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder - ``` - -### Futuro (v1.2.0) - -- [ ] Notifiche email per aste vinte -- [ ] Export statistiche CSV/Excel -- [ ] Dashboard mobile-responsive -- [ ] API REST pubblica - ---- - -## ?? Metriche Release - -### File - -- **Nuovi:** 13 file documentazione/script -- **Modificati:** 4 file sorgente -- **Righe totali:** ~3500+ righe documentazione - -### Problemi Risolti - -- ? Errore Visual Studio "ContainerBuild" -- ? Crash container certificati HTTPS -- ? Convenzione path Gitea errata -- ? Mancanza sistema versionamento -- ? Workflow pubblicazione manuale - -### Funzionalità Aggiunte - -- ? Pubblicazione automatica Gitea -- ? Versionamento semantico -- ? Script automazione versione -- ? Documentazione completa - ---- - -## ?? STATO FINALE - -``` -????????????????????????????????????????????????????????????????????? -? ? -? ? RELEASE v1.1.0 COMPLETATA CON SUCCESSO! ? -? ? -? • Sistema versionamento implementato ? -? • Workflow Gitea automatizzato ? -? • Container HTTPS fix applicato ? -? • Visual Studio funzionante ? -? • Documentazione completa ? -? ? -? ?? Immagini disponibili su: ? -? gitea.encke-hake.ts.net/alby96/autobidder:latest ? -? gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 ? -? ? -????????????????????????????????????????????????????????????????????? -``` - -**?? Sistema pronto per production deployment!** - ---- - -**Data completamento:** 2025-01-18 -**Versione:** 1.1.0 -**Tipo release:** MINOR (feature + bugfix) -**Stato:** ? PRODUCTION READY diff --git a/Mimante/RIEPILOGO_SICUREZZA_v1.2.0.md b/Mimante/RIEPILOGO_SICUREZZA_v1.2.0.md deleted file mode 100644 index ba7c808..0000000 --- a/Mimante/RIEPILOGO_SICUREZZA_v1.2.0.md +++ /dev/null @@ -1,427 +0,0 @@ -# ?? RIEPILOGO IMPLEMENTAZIONE SICUREZZA v1.2.0 - -## ? IMPLEMENTAZIONE COMPLETATA - -Sistema di autenticazione enterprise-grade implementato in AutoBidder per deploy sicuro su Tailscale. - ---- - -## ?? Cosa È Stato Fatto - -### 1. ? Sistema Autenticazione ASP.NET Core Identity - -**File creati/modificati:** -- `Models/ApplicationUser.cs` - Modello utente esteso -- `Data/ApplicationDbContext.cs` - DbContext Identity -- `Pages/Login.razor` - Pagina login styled -- `Pages/Logout.razor` - Pagina logout -- `Program.cs` - Configurazione Identity + middleware -- `Shared/NavMenu.razor` - Indicatore utente + logout - -### 2. ? Protezione Route - -**Pagine protette con `[Authorize]`:** -- ? `Pages/Index.razor` (Monitor Aste) -- ? `Pages/FreeBids.razor` (Puntate Gratuite) -- ? `Pages/Statistics.razor` (Statistiche) -- ? `Pages/Settings.razor` (Impostazioni) -- ? `Pages/Health.razor` (Health Check) - -**Pagine pubbliche:** -- ? `/login` - Accesso -- ? `/logout` - Disconnessione - -### 3. ? Database Identity - -``` -Percorso: /app/Data/identity.db -Tipo: SQLite -Persistente: Sì (volume Docker) -Inizializzazione: Automatica al primo avvio -Seed admin: Automatico con credenziali da env vars -``` - -### 4. ? Configurazione Sicurezza - -**Cookie policy:** -```csharp -HttpOnly = true // Anti-XSS -SameSite = Lax // Anti-CSRF -SecurePolicy = SameAsRequest // Tailscale HTTP OK -ExpireTimeSpan = 7 days -SlidingExpiration = true -``` - -**Password policy:** -``` -Min Length: 12 caratteri -RequireDigit: true -RequireLowercase: true -RequireUppercase: true -RequireNonAlphanumeric: true -RequiredUniqueChars: 4 -``` - -**Lockout policy:** -``` -MaxFailedAccessAttempts: 5 -DefaultLockoutTimeSpan: 15 minuti -AllowedForNewUsers: true -``` - -### 5. ? Environment Variables - -**docker-compose.yml:** -```yaml -environment: - - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} - - ADMIN_PASSWORD=${ADMIN_PASSWORD} -``` - -**.env.example:** -```bash -ADMIN_USERNAME=admin -ADMIN_PASSWORD= # DA CONFIGURARE! -``` - -### 6. ? Documentazione - -**File creati:** -- `SECURITY.md` - Guida completa sicurezza (comprehensive) -- `CHANGELOG.md` - Aggiornato con v1.2.0 -- `README.md` - Aggiornato con sezione sicurezza -- `.env` - File configurazione template - ---- - -## ?? Come Funziona - -### Flusso Autenticazione - -``` -1. Utente accede a http://192.168.30.23:8889 -2. AutoBidder verifica autenticazione -3. Se NON autenticato ? redirect /login -4. Utente inserisce username/password -5. ASP.NET Core Identity valida: - - Password policy - - Lockout status - - Account attivo -6. Se valido: - - Crea cookie sicuro - - Redirect alla pagina richiesta -7. Cookie valido per 7 giorni (sliding) -``` - -### Protezione Brute-Force - -``` -Tentativo 1-4: Login fallito -Tentativo 5: Account lockout (15 min) -Tentativo 6: "Account temporarily blocked" -Dopo 15 min: Lockout automaticamente rimosso -``` - -### Gestione Sessioni - -``` -Cookie lifetime: 7 giorni -Sliding expiration: Sì (rinnovo automatico) -Inattività max: ~7 giorni -Logout: Distruzione cookie immediata -``` - ---- - -## ?? Configurazione Deployment - -### Unraid - -``` -Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 - -Port Mappings: - Container Port: 8080 - Host Port: 8889 - -Environment Variables: - ADMIN_USERNAME=admin - ADMIN_PASSWORD=MyS3cur3P@ss!2024 - ASPNETCORE_ENVIRONMENT=Production - -Volumes: - Container Path: /app/Data - Host Path: /mnt/user/appdata/autobidder/data -``` - -### Docker Compose - -```yaml -services: - autobidder: - image: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 - ports: - - "8889:8080" - environment: - - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} - - ADMIN_PASSWORD=${ADMIN_PASSWORD} - volumes: - - ./Data:/app/Data -``` - -### Tailscale - -```bash -# Esponi su Tailscale (opzionale, per HTTPS) -tailscale serve --bg --https=8443 http://localhost:8080 - -# Accedi via Tailscale hostname -https://autobidder.tailnet-XXXX.ts.net -``` - ---- - -## ?? Primo Avvio - -### 1. Configura Password - -```bash -# .env -ADMIN_USERNAME=admin -ADMIN_PASSWORD=MyS3cur3P@ssw0rd!2024 -``` - -### 2. Build Immagine - -```bash -docker build -t autobidder:1.2.0 . -``` - -### 3. Avvia Container - -```bash -docker run -d \ - --name AutoBidder \ - -p 8889:8080 \ - -e ADMIN_USERNAME=admin \ - -e ADMIN_PASSWORD="MyS3cur3P@ss!2024" \ - -v /data:/app/Data \ - autobidder:1.2.0 -``` - -### 4. Verifica Log - -```bash -docker logs AutoBidder | grep "\[Identity\]" - -# Output atteso: -[Identity] Database initialized -[Identity] Admin user created: admin -``` - -### 5. Primo Login - -``` -Browser: http://192.168.30.23:8889 - ? -Redirect automatico a /login - ? -Username: admin -Password: MyS3cur3P@ss!2024 - ? -Click "Accedi" - ? -? Homepage AutoBidder -``` - ---- - -## ?? Password Temporanea (Default) - -### ?? SE NON CONFIGURI ADMIN_PASSWORD - -**Username:** `admin` -**Password:** `Admin@Password123!` - -**WARNING nei log:** -``` -[Identity] WARNING: ADMIN_PASSWORD not set! Using default password. -[Identity] CHANGE IT IMMEDIATELY after first login! -[Identity] Admin user created: admin -[Identity] ?? REMEMBER TO CHANGE THE DEFAULT PASSWORD! -``` - -**?? CAMBIARE IMMEDIATAMENTE!** - -(Funzione cambio password sarà aggiunta in v1.2.1) - ---- - -## ?? Test Sicurezza - -### Test 1: Login Riuscito - -``` -Username: admin -Password: (corretta) -Result: ? Accesso consentito -``` - -### Test 2: Password Sbagliata - -``` -Username: admin -Password: wrong -Result: ? "Username o password non validi" -``` - -### Test 3: Brute-Force Protection - -``` -Tentativi: 5x password sbagliata -Result: ? "Account temporaneamente bloccato per troppi tentativi falliti" -Wait: 15 minuti -Result: ? Lockout rimosso, può ritentare -``` - -### Test 4: Protezione Route - -``` -Browser: http://192.168.30.23:8889/ -Stato: Non autenticato -Result: ? Redirect a /login -``` - -### Test 5: Sessione Persistente - -``` -1. Login con "Ricordami" ? -2. Chiudi browser -3. Riapri dopo 1 ora -4. Vai a homepage -Result: ? Ancora autenticato (cookie valido) -``` - ---- - -## ? Checklist Completa - -### Implementazione -- [x] ASP.NET Core Identity configurato -- [x] ApplicationUser model creato -- [x] ApplicationDbContext creato -- [x] Pagina Login styled -- [x] Pagina Logout -- [x] Protezione route con [Authorize] -- [x] Cookie sicuri configurati -- [x] Password policy forte -- [x] Lockout brute-force -- [x] Seed utente admin -- [x] Environment variables -- [x] NavMenu con logout - -### Docker -- [x] docker-compose.yml aggiornato -- [x] .env.example creato -- [x] .env template creato -- [x] Healthcheck compatibile -- [x] Volume /app/Data persistente -- [x] Build test superato - -### Documentazione -- [x] SECURITY.md completa -- [x] CHANGELOG.md aggiornato -- [x] README.md aggiornato -- [x] Versione incrementata (1.2.0) -- [x] Questo riepilogo - ---- - -## ?? File Creati/Modificati - -### Nuovi File (11) -- `Models/ApplicationUser.cs` -- `Data/ApplicationDbContext.cs` -- `Pages/Login.razor` -- `Pages/Logout.razor` -- `SECURITY.md` -- `RIEPILOGO_SICUREZZA_v1.2.0.md` -- `.env` - -### File Modificati (9) -- `Program.cs` (Identity + middleware) -- `AutoBidder.csproj` (package Identity) -- `Shared/NavMenu.razor` (logout + user info) -- `Pages/Index.razor` ([Authorize]) -- `Pages/FreeBids.razor` ([Authorize]) -- `Pages/Statistics.razor` ([Authorize]) -- `Pages/Settings.razor` ([Authorize]) -- `Pages/Health.razor` ([Authorize]) -- `docker-compose.yml` (env vars) -- `.env.example` (credenziali) -- `README.md` (sezione sicurezza) -- `CHANGELOG.md` (v1.2.0) -- `Dockerfile` (versione 1.2.0) - ---- - -## ?? Prossimi Passi - -### Per l'Utente - -1. **Configura password in `.env`:** - ```bash - cp .env.example .env - nano .env # Imposta ADMIN_PASSWORD - ``` - -2. **Pubblica nuova immagine:** - ```bash - # Visual Studio ? Pubblica ? GiteaRegistry - # Oppure: - docker build -t gitea.../autobidder:1.2.0 . - docker push gitea.../autobidder:1.2.0 - ``` - -3. **Deploy su Unraid:** - - Stop container vecchio - - Pull immagine `1.2.0` - - Aggiungi env vars: `ADMIN_USERNAME`, `ADMIN_PASSWORD` - - Start container - - Primo login - -### Per lo Sviluppatore (Futuro) - -**v1.2.1:** -- [ ] Pagina cambio password utente -- [ ] Gestione profilo utente -- [ ] Visualizzazione ultimo accesso - -**v1.3.0:** -- [ ] Multi-utente (admin + users) -- [ ] Ruoli e permessi -- [ ] Log audit accessi -- [ ] 2FA opzionale - -**v2.0.0:** -- [ ] OAuth2/OIDC (Tailscale) -- [ ] SSO integration -- [ ] LDAP/AD support - ---- - -## ? IMPLEMENTAZIONE COMPLETA E TESTATA! - -**?? AutoBidder v1.2.0** è ora protetto con autenticazione enterprise-grade, pronto per deploy production su Tailscale! - -**Sicurezza implementata:** -- ? Login username/password -- ? Protezione brute-force -- ? Cookie sicuri -- ? Password policy forte -- ? Protezione route -- ? Database Identity persistente -- ? Seed admin automatico -- ? Documentazione completa - -**?? Pronto per pubblicazione e deployment!** diff --git a/Mimante/RIMOZIONE_CREDENZIALI_BIDOO.md b/Mimante/RIMOZIONE_CREDENZIALI_BIDOO.md deleted file mode 100644 index 69ee6be..0000000 --- a/Mimante/RIMOZIONE_CREDENZIALI_BIDOO.md +++ /dev/null @@ -1,261 +0,0 @@ -# ? RIMOSSI PARAMETRI CREDENZIALI BIDOO - -## ?? Modifiche Applicate - -### Motivazione - -Le credenziali Bidoo (username/password) **NON sono necessarie** perché l'applicazione usa il **cookie di sessione** incollato manualmente dall'interfaccia web. - ---- - -## ?? File Modificati - -### 1. **Dockerfile** -```docker -# RIMOSSO: -ENV BIDOO_USERNAME= -ENV BIDOO_PASSWORD= - -# MANTENUTO: -ENV ADMIN_USERNAME=admin -ENV ADMIN_PASSWORD= -``` - -### 2. **docker-compose.yml** -```yaml -# RIMOSSO: -- BIDOO_USERNAME=${BIDOO_USERNAME} -- BIDOO_PASSWORD=${BIDOO_PASSWORD} - -# MANTENUTO: -- ADMIN_USERNAME=${ADMIN_USERNAME:-admin} -- ADMIN_PASSWORD=${ADMIN_PASSWORD} -``` - -### 3. **.env.example** -```bash -# RIMOSSO: -BIDOO_USERNAME= -BIDOO_PASSWORD= - -# AGGIUNTO commento: -# === NOTA: SESSIONE BIDOO === -# Il cookie si configura dall'interfaccia web -# Settings ? Sessione Bidoo ? Incolla cookie -``` - -### 4. **.env** -```bash -# RIMOSSO: -BIDOO_USERNAME= -BIDOO_PASSWORD= - -# AGGIUNTO: -# === NOTA: SESSIONE BIDOO === -# Si configura dall'interfaccia web -``` - -### 5. **UNRAID_TEMPLATE.md** - -**XML Template - Rimossi parametri:** -```xml - - - -``` - -**Documentazione aggiornata:** -```markdown -#### ?? Sessione Bidoo - -NON servono credenziali qui! - -Il cookie si configura dall'interfaccia web: -1. Login su AutoBidder -2. Settings ? Sessione Bidoo -3. Incolla cookie -4. Salva -``` - -### 6. **QUICKSTART_SECURITY.md** - -**Rimossa sezione:** -```markdown -### 2. Credenziali Bidoo (Funzionamento) -BIDOO_USERNAME=... -BIDOO_PASSWORD=... -``` - -**Aggiunto Step 5:** -```markdown -### Step 5: Configura Sessione Bidoo (1 minuto) - -1. Settings ? Sessione Bidoo -2. Incolla cookie -3. Salva -``` - -### 7. **SECURITY.md** - -**Esempi aggiornati:** -- Rimossi parametri `BIDOO_USERNAME` e `BIDOO_PASSWORD` -- Aggiunta nota: "Si configura dall'interfaccia web" - ---- - -## ? Configurazione Finale - -### Environment Variables Richieste - -```bash -# SOLO AUTENTICAZIONE APPLICAZIONE -ADMIN_USERNAME=admin -ADMIN_PASSWORD=TuaPasswordSicura123! - -# Database (opzionale) -POSTGRES_USER=autobidder -POSTGRES_PASSWORD=autobidder_password -USE_POSTGRES=true -``` - -### Configurazione Sessione Bidoo - -**Dall'interfaccia web dopo login:** - -1. **Login su AutoBidder** - - Username: `admin` - - Password: (valore `ADMIN_PASSWORD`) - -2. **Vai su Settings** - - Click menu: **Settings** - -3. **Sezione Sessione Bidoo** - - Campo: "Cookie di sessione" - - Incolla cookie ottenuto da Bidoo.it - - Click: **Salva** - -4. **Verifica connessione** - - Homepage ? monitoring aste dovrebbe funzionare - ---- - -## ?? Come Ottenere Cookie Bidoo - -### Browser Desktop - -``` -1. Apri Bidoo.it -2. Fai login con le tue credenziali -3. Premi F12 (Developer Tools) -4. Tab "Application" (Chrome) o "Storage" (Firefox) -5. Cookies ? https://bidoo.it -6. Cerca cookie di sessione (es. "session_id", "auth_token") -7. Copia il valore -8. Incolla in AutoBidder Settings -``` - -### Chrome Mobile - -``` -1. Bidoo.it ? Login -2. Chrome menu (?) ? More tools ? Developer tools -3. Application ? Cookies -4. Copia valore cookie sessione -``` - ---- - -## ?? Unraid - Esempio Configurazione - -### Environment Variables (SOLO ADMIN) - -``` -ADMIN_USERNAME = admin -ADMIN_PASSWORD = MyS3cur3P@ss!2024 -ASPNETCORE_ENVIRONMENT = Production -``` - -**NON servono altri parametri!** - -### Primo Avvio - -``` -1. Start container -2. Browser: http://IP:8889 -3. Login: admin / password -4. Settings ? Sessione Bidoo -5. Incolla cookie -6. ? Monitoring attivo! -``` - ---- - -## ?? Vantaggi Approccio Cookie - -### ? Pro - -- **Più sicuro:** Nessuna password Bidoo memorizzata nel container -- **Più semplice:** Meno parametri da configurare -- **Più flessibile:** Cookie può essere aggiornato senza restart container -- **Più privacy:** Password Bidoo non visibile nei log Docker - -### ?? Contro - -- **Setup manuale:** Utente deve ottenere cookie da browser -- **Scadenza:** Cookie potrebbe scadere (ma può essere aggiornato) - -### ?? Scadenza Cookie - -**Se cookie scade:** -1. AutoBidder mostrerà errore connessione Bidoo -2. Vai su Settings -3. Ottieni nuovo cookie da Bidoo.it -4. Incolla e salva -5. ? Risolto! - ---- - -## ?? Checklist Aggiornamento - -- [x] Rimossi `BIDOO_USERNAME` e `BIDOO_PASSWORD` da Dockerfile -- [x] Rimossi da docker-compose.yml -- [x] Rimossi da .env.example -- [x] Rimossi da .env -- [x] Aggiornato UNRAID_TEMPLATE.md (XML + docs) -- [x] Aggiornato QUICKSTART_SECURITY.md -- [x] Aggiornato SECURITY.md -- [x] Aggiunte note "Configurazione dall'interfaccia web" -- [x] Documentato come ottenere cookie Bidoo -- [x] Build test superato ? - ---- - -## ?? Prossimi Passi - -### Per l'Utente - -1. **Se hai già deployato v1.2.0 con credenziali Bidoo:** - - Non serve fare niente! - - Parametri `BIDOO_*` verranno ignorati - -2. **Nuovo deploy:** - - Configura solo `ADMIN_PASSWORD` - - Dopo login, incolla cookie Bidoo in Settings - -### Per lo Sviluppatore - -**Nessuna modifica codice necessaria!** - -L'app già supporta l'incollatura manuale del cookie dall'interfaccia Settings. - ---- - -## ? COMPLETATO - -**Configurazione semplificata:** -- ? SOLO 2 parametri obbligatori: `ADMIN_USERNAME`, `ADMIN_PASSWORD` -- ? Cookie Bidoo configurato dall'interfaccia web -- ? Template Unraid pulito e semplice -- ? Documentazione aggiornata - -**?? Deploy più facile e sicuro!** diff --git a/Mimante/SECURITY.md b/Mimante/SECURITY.md deleted file mode 100644 index c78c0f1..0000000 --- a/Mimante/SECURITY.md +++ /dev/null @@ -1,411 +0,0 @@ -# ?? GUIDA SICUREZZA - AutoBidder v1.2.0 - -## ?? Sistema di Autenticazione Implementato - -AutoBidder v1.2.0 include un sistema di autenticazione completo basato su **ASP.NET Core Identity**, progettato specificamente per l'esposizione sicura tramite Tailscale. - ---- - -## ? Feature di Sicurezza - -### 1. ?? Autenticazione Utente - -- **ASP.NET Core Identity** integrato -- Login con username e password -- Sessioni sicure con cookie HttpOnly -- Logout sicuro - -### 2. ??? Protezione Brute-Force - -```csharp -// Configurazione automatica: -- Max tentativi falliti: 5 -- Timeout lockout: 15 minuti -- Lockout abilitato per tutti gli utenti -``` - -### 3. ?? Password Policy Forte - -**Requisiti obbligatori:** -- ? Minimo 12 caratteri -- ? Almeno 1 maiuscola -- ? Almeno 1 minuscola -- ? Almeno 1 numero -- ? Almeno 1 simbolo speciale -- ? Minimo 4 caratteri unici - -**Esempi password valide:** -``` -? MyS3cur3P@ssw0rd!2024 -? Admin@SecurePass123! -? Bidoo#Manager2024$ -? password123 (troppo semplice) -? Admin123 (manca simbolo, troppo corta) -``` - -### 4. ?? Cookie Sicuri - -```csharp -Cookie Configuration: -- HttpOnly: true (protezione XSS) -- SameSite: Lax (protezione CSRF) -- SecurePolicy: SameAsRequest (Tailscale HTTP OK) -- Durata: 7 giorni (sliding expiration) -``` - -### 5. ?? Protezione Route - -Tutte le pagine protette con `[Authorize]`: -- `/` (Monitor Aste) -- `/freebids` (Puntate Gratuite) -- `/statistics` (Statistiche) -- `/settings` (Impostazioni) -- `/health` (Health Check) - -**Pagine pubbliche:** -- `/login` ? -- `/logout` ? - ---- - -## ?? Configurazione - -### 1. File `.env` (OBBLIGATORIO) - -```bash -# Copia .env.example in .env -cp .env.example .env - -# Modifica password admin: -ADMIN_USERNAME=admin -ADMIN_PASSWORD=TuaPasswordSicura123! -``` - -**Nota:** Le credenziali Bidoo NON servono qui. Il cookie di sessione si configura dall'interfaccia web dopo il login. - -### 2. Docker Compose - -```yaml -services: - autobidder: - environment: - # Autenticazione applicazione - - ADMIN_USERNAME=${ADMIN_USERNAME:-admin} - - ADMIN_PASSWORD=${ADMIN_PASSWORD} -``` - -**Sessione Bidoo:** Configurata dall'interfaccia web (Settings). - -### 3. Unraid / Docker Run - -```bash -docker run -d \ - --name AutoBidder \ - -p 8889:8080 \ - -e ADMIN_USERNAME=admin \ - -e ADMIN_PASSWORD="MyS3cur3P@ss!" \ - -v /data:/app/Data \ - gitea.../autobidder:1.2.0 -``` - -**Dopo il primo login:** -- Settings ? Sessione Bidoo ? Incolla cookie - -e ADMIN_PASSWORD="MyS3cur3P@ss!" \ - -v /data:/app/Data \ - gitea.../autobidder:1.2.0 -``` - ---- - -## ?? Primo Avvio - -### Step 1: Configura Password - -**Opzione A: Password personalizzata (CONSIGLIATO)** - -```bash -# .env -ADMIN_USERNAME=admin -ADMIN_PASSWORD=MyS3cur3P@ssw0rd!2024 -``` - -**Opzione B: Password temporanea default** - -Se `ADMIN_PASSWORD` non è settata: -- Username: `admin` -- Password: `Admin@Password123!` -- ?? **CAMBIARE IMMEDIATAMENTE!** - -### Step 2: Avvia Container - -```bash -docker-compose up -d -``` - -### Step 3: Verifica Log - -```bash -docker logs AutoBidder | grep "\[Identity\]" - -# Output atteso: -[Identity] Database initialized -[Identity] Admin user created: admin -``` - -### Step 4: Primo Login - -1. Apri browser: `http://192.168.30.23:8889` -2. Verrai reindirizzato a `/login` -3. Inserisci credenziali: - - Username: `admin` (o valore ADMIN_USERNAME) - - Password: (valore ADMIN_PASSWORD) -4. Click "Accedi" - -**Se password temporanea usata:** -- ?? Cambia password IMMEDIATAMENTE! -- (Funzione cambio password sarà aggiunta in v1.2.1) - ---- - -## ?? Gestione Utenti - -### Database Identity - -``` -Percorso: /app/Data/identity.db -Tipo: SQLite -Tabelle: - - Users (utenti applicazione) - - Roles (ruoli - futuro) - - UserLogins (log accessi - futuro) -``` - -### Backup Database Utenti - -```bash -# Backup manuale -docker cp AutoBidder:/app/Data/identity.db ./backup/identity-$(date +%Y%m%d).db - -# Verifica backup -sqlite3 ./backup/identity-*.db "SELECT UserName, CreatedAt FROM Users;" -``` - -### Reset Password Admin - -Se hai dimenticato la password: - -```bash -# 1. Stop container -docker stop AutoBidder - -# 2. Elimina database Identity -docker exec AutoBidder rm /app/Data/identity.db - -# 3. Riconfigura password in .env -echo "ADMIN_PASSWORD=NuovaPassword123!" >> .env - -# 4. Restart container (creerà nuovo database) -docker start AutoBidder - -# 5. Verifica log -docker logs AutoBidder | grep "\[Identity\]" -``` - ---- - -## ??? Best Practices Sicurezza - -### 1. Password Forte - -```bash -# Genera password sicura (Linux/Mac): -openssl rand -base64 32 - -# Oppure usa password manager: -- LastPass -- 1Password -- Bitwarden -``` - -### 2. Rotazione Periodica - -```bash -# Ogni 90 giorni: -1. Genera nuova password -2. Aggiorna .env -3. Restart container -4. Verifica accesso -``` - -### 3. Monitoraggio Accessi - -```bash -# Controlla tentativi falliti: -docker logs AutoBidder | grep "password non validi" - -# Controlla lockout: -docker logs AutoBidder | grep "temporarily blocked" - -# Controlla accessi riusciti: -docker logs AutoBidder | grep "Login successful" -``` - -### 4. Limitazione Accesso Rete - -```bash -# Solo Tailscale (consigliato): -tailscale serve --bg --https=8443 http://localhost:8080 - -# Firewall (se non usi Tailscale): -ufw allow from 100.64.0.0/10 to any port 8080 # Solo Tailscale IP -ufw deny 8080 # Blocca tutto il resto -``` - -### 5. HTTPS con Reverse Proxy - -```nginx -# Nginx su Tailscale -server { - listen 443 ssl http2; - server_name autobidder.tailnet-XXXX.ts.net; - - ssl_certificate /etc/tailscale/cert.pem; - ssl_certificate_key /etc/tailscale/key.pem; - - location / { - proxy_pass http://localhost:8080; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -``` - ---- - -## ?? Troubleshooting - -### Problema: "Account temporaneamente bloccato" - -**Causa:** Troppi tentativi falliti (5) - -**Soluzione:** -```bash -# Aspetta 15 minuti (lockout automatico) -# Oppure reset database Identity (vedi sopra) -``` - -### Problema: "Username o password non validi" - -**Verifica:** -1. Controlla `.env` per ADMIN_PASSWORD -2. Verifica maiuscole/minuscole -3. Controlla log container - -```bash -docker logs AutoBidder | grep "\[Identity\]" -``` - -### Problema: Redirect loop `/login` - -**Causa:** Cookie non accettati dal browser - -**Soluzione:** -1. Abilita cookie nel browser -2. Usa browser diverso -3. Controlla log console browser (F12) - -### Problema: Password non accettata - -**Verifica requisiti:** -- ? Min 12 caratteri? -- ? Maiuscola presente? -- ? Minuscola presente? -- ? Numero presente? -- ? Simbolo presente? - ---- - -## ?? Metriche Sicurezza - -### Audit Log - -```bash -# Ultimi accessi: -docker logs AutoBidder --since 24h | grep "\[Identity\]" - -# Tentativi falliti oggi: -docker logs AutoBidder --since 1d | grep "password non validi" - -# Lockout oggi: -docker logs AutoBidder --since 1d | grep "temporarily blocked" -``` - -### Statistiche Utenti - -```bash -# Connetti al database: -docker exec -it AutoBidder sqlite3 /app/Data/identity.db - -# Query utenti: -SELECT UserName, CreatedAt, LastLoginAt, IsActive -FROM Users; - -# Exit: -.exit -``` - ---- - -## ?? Roadmap Sicurezza - -### v1.2.1 (Prossima) -- [ ] Cambio password utente -- [ ] Gestione multi-utente -- [ ] Ruoli (Admin/User) -- [ ] Log audit accessi - -### v1.3.0 (Futuro) -- [ ] 2FA (Two-Factor Authentication) -- [ ] OAuth2/OIDC (Tailscale) -- [ ] IP whitelisting -- [ ] Session timeout configurabile - ---- - -## ? Checklist Sicurezza - -Prima del deploy production: - -- [ ] Password forte configurata in `.env` -- [ ] `.env` in `.gitignore` (non committare!) -- [ ] Backup database Identity configurato -- [ ] Monitoraggio log attivo -- [ ] Tailscale ACL configurato (solo utenti autorizzati) -- [ ] Firewall configurato (solo Tailscale) -- [ ] Reverse proxy HTTPS (opzionale) -- [ ] Password rotation calendar (ogni 90 giorni) - ---- - -## ?? Supporto - -**Problemi di sicurezza:** -- Apri issue su Gitea (segnala vulnerabilità in privato) -- Controlla log: `docker logs AutoBidder` -- Verifica configurazione: `docker inspect AutoBidder` - -**Documentazione:** -- `CHANGELOG.md` - Note release -- `README.md` - Overview progetto -- `DOCKER_PUBLISH_GUIDE.md` - Deployment - ---- - -**?? AutoBidder v1.2.0 - Sicuro per produzione con Tailscale!** - -Sistema di autenticazione enterprise-grade per proteggere i tuoi dati di asta. diff --git a/Mimante/Services/AuctionMonitor.cs b/Mimante/Services/AuctionMonitor.cs index 3638448..8938fc7 100644 --- a/Mimante/Services/AuctionMonitor.cs +++ b/Mimante/Services/AuctionMonitor.cs @@ -4,16 +4,19 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using AutoBidder.Models; +using AutoBidder.Utilities; namespace AutoBidder.Services { /// /// Servizio centrale per monitoraggio aste /// Sistema di timing ottimizzato: punta solo se necessario, poco prima della scadenza + /// Integra BidStrategyService per strategie avanzate /// public class AuctionMonitor { private readonly BidooApiClient _apiClient; + private readonly BidStrategyService _bidStrategy; private readonly List _auctions = new(); private CancellationTokenSource? _monitoringCts; private Task? _monitoringTask; @@ -29,9 +32,10 @@ namespace AutoBidder.Services ///
public event Action? OnAuctionCompleted; - public AuctionMonitor() + public AuctionMonitor(BidStrategyService? bidStrategy = null) { _apiClient = new BidooApiClient(); + _bidStrategy = bidStrategy ?? new BidStrategyService(); _apiClient.OnAuctionLog += (auctionId, message) => { @@ -334,6 +338,7 @@ namespace AutoBidder.Services return false; } + private async Task PollAndProcessAuction(AuctionInfo auction, CancellationToken token) { try @@ -347,10 +352,17 @@ namespace AutoBidder.Services return; } - - auction.PollingLatencyMs = state.PollingLatencyMs; + // ?? Aggiorna latenza con storico + auction.AddLatencyMeasurement(state.PollingLatencyMs); - // ? AGGIORNATO: Aggiorna storia puntate mantenendo quelle vecchie + // ?? Segna tracking dall'inizio se è la prima volta + if (!auction.IsTrackedFromStart && auction.BidHistory.Count == 0) + { + auction.IsTrackedFromStart = true; + auction.TrackingStartedAt = DateTime.UtcNow; + } + + // Aggiorna storia puntate mantenendo quelle vecchie if (state.RecentBidsHistory != null && state.RecentBidsHistory.Count > 0) { MergeBidHistory(auction, state.RecentBidsHistory); @@ -365,6 +377,32 @@ namespace AutoBidder.Services bool won = state.Status == AuctionStatus.EndedWon; + // ?? FIX: Aggiungi ultima puntata mancante a RecentBids + // L'API spesso non include l'ultima puntata nella storia + if (!string.IsNullOrEmpty(state.LastBidder) && state.Price > 0) + { + var lastBidTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var statePrice = (decimal)state.Price; + + // Verifica se questa puntata non è già presente + var alreadyExists = auction.RecentBids.Any(b => + Math.Abs(b.Price - statePrice) < 0.001m && + b.Username.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase)); + + if (!alreadyExists) + { + auction.RecentBids.Insert(0, new BidHistoryEntry + { + Username = state.LastBidder, + Price = statePrice, + Timestamp = lastBidTimestamp, + BidType = "Auto" + }); + + auction.AddLog($"[FIX] Aggiunta ultima puntata mancante: {state.LastBidder} €{state.Price:F2}"); + } + } + auction.IsActive = false; auction.LastState = state; // Salva stato finale per statistiche auction.AddLog($"[ASTA TERMINATA] {statusMsg}"); @@ -464,28 +502,137 @@ namespace AutoBidder.Services } /// - /// Strategia di puntata ottimizzata: punta solo quando necessario + /// Strategia di puntata ottimizzata con BidStrategyService + /// Usa: adaptive latency, jitter, dynamic offset, heat metric, competition detection /// private async Task ExecuteBidStrategy(AuctionInfo auction, AuctionState state, CancellationToken token) { + var settings = SettingsManager.Load(); + // Calcola il tempo rimanente in millisecondi double timerMs = state.Timer * 1000; - // Se siamo nella finestra di puntata (timer <= BidBeforeDeadlineMs) - if (timerMs <= auction.BidBeforeDeadlineMs) + // ??? CONTROLLO: Se sono già il vincitore, non fare nulla + if (state.IsMyBid) { + return; + } + + // ?? AGGIORNA METRICHE (solo se strategie avanzate abilitate) + if (auction.AdvancedStrategiesEnabled != false) + { + var session = _apiClient.GetSession(); + var currentUsername = session?.Username ?? ""; + + _bidStrategy.UpdateHeatMetric(auction, settings, currentUsername); + + // Verifica strategie avanzate (soft retreat, competition, probabilistic, etc.) + var decision = _bidStrategy.ShouldPlaceBid(auction, state, settings, currentUsername); + + if (!decision.ShouldBid) + { + auction.AddLog($"[STRATEGY] {decision.Reason}"); + return; + } + } + + // ?? CALCOLA TIMING OTTIMALE + var timing = _bidStrategy.CalculateOptimalTiming(auction, settings); + int effectiveOffset = timing.FinalOffsetMs; + + // ?? TIMER-BASED SCHEDULING + if (timerMs > effectiveOffset) + { + // Timer ancora alto ? Schedula puntata futura + double delayMs = timerMs - effectiveOffset; + + // Non schedulare se già c'è un task attivo per questa asta + if (auction.IsAttackInProgress) + { + return; // Task già schedulato + } + auction.IsAttackInProgress = true; + auction.LastUsedOffsetMs = effectiveOffset; + + // Log con dettagli timing (solo se logging avanzato) + if (settings.AdvancedLoggingEnabled) + { + auction.AddLog($"[TIMING] Timer={timerMs:F0}ms, Offset={effectiveOffset}ms (base={timing.BaseOffsetMs}+lat={timing.LatencyCompensationMs}+dyn={timing.DynamicAdjustmentMs}+jit={timing.JitterMs}) ? Delay={delayMs:F0}ms"); + } + else + { + auction.AddLog($"[STRATEGIA] Timer={timerMs:F0}ms ? Puntata tra {delayMs:F0}ms (offset={effectiveOffset}ms)"); + } + + // Avvia task asincrono che attende e poi punta + _ = Task.Run(async () => + { + try + { + // Attendi il momento esatto + await Task.Delay((int)delayMs, token); + + // Verifica che l'asta sia ancora attiva e non in pausa + if (!auction.IsActive || auction.IsPaused || token.IsCancellationRequested) + { + auction.AddLog($"[STRATEGIA] Task annullato (asta inattiva/pausa)"); + return; + } + + // Verifica soft retreat + if (auction.IsInSoftRetreat) + { + auction.AddLog($"[STRATEGIA] Task annullato (soft retreat attivo)"); + return; + } + + // Controlla se qualcun altro ha puntato di recente + var lastBidTime = GetLastBidTime(auction, state.LastBidder); + if (lastBidTime.HasValue) + { + var timeSinceLastBid = DateTime.UtcNow - lastBidTime.Value; + if (timeSinceLastBid.TotalMilliseconds < 500) + { + auction.AddLog($"[COLLISION] Puntata recente di {state.LastBidder} ({timeSinceLastBid.TotalMilliseconds:F0}ms fa)"); + _bidStrategy.RecordBidAttempt(auction, false, collision: true); + return; + } + } + + auction.AddLog($"[STRATEGIA] Task eseguito ? PUNTA ORA!"); + + // Esegui la puntata + await ExecuteBid(auction, state, token); + } + catch (OperationCanceledException) + { + auction.AddLog($"[STRATEGIA] Task cancellato"); + } + catch (Exception ex) + { + auction.AddLog($"[STRATEGIA ERROR] {ex.Message}"); + } + finally + { + auction.IsAttackInProgress = false; + } + }, token); + } + else if (timerMs > 0 && timerMs <= effectiveOffset) + { + // Timer già nella finestra ? Punta SUBITO senza delay + if (auction.IsAttackInProgress) + { + return; // Già in corso + } + + auction.IsAttackInProgress = true; + auction.LastUsedOffsetMs = effectiveOffset; try { - auction.AddLog($"[STRATEGIA] Finestra di puntata raggiunta: {timerMs:F0}ms <= {auction.BidBeforeDeadlineMs}ms"); - - // ? NUOVO: Controlla se sono già io il vincitore corrente - if (state.IsMyBid) - { - auction.AddLog($"[STRATEGIA] SKIP: Sono già il vincitore corrente (ultimo bidder: {state.LastBidder})"); - return; - } + auction.AddLog($"[STRATEGIA] Timer già in finestra ({timerMs:F0}ms <= {effectiveOffset}ms) ? PUNTA SUBITO!"); // Controlla se qualcun altro ha puntato di recente var lastBidTime = GetLastBidTime(auction, state.LastBidder); @@ -494,7 +641,8 @@ namespace AutoBidder.Services var timeSinceLastBid = DateTime.UtcNow - lastBidTime.Value; if (timeSinceLastBid.TotalMilliseconds < 500) { - auction.AddLog($"[STRATEGIA] Puntata recente di {state.LastBidder} ({timeSinceLastBid.TotalMilliseconds:F0}ms fa), attendo..."); + auction.AddLog($"[COLLISION] Puntata recente di {state.LastBidder} ({timeSinceLastBid.TotalMilliseconds:F0}ms fa)"); + _bidStrategy.RecordBidAttempt(auction, false, collision: true); return; } } @@ -507,40 +655,29 @@ namespace AutoBidder.Services auction.IsAttackInProgress = false; } } + // Se timer <= 0, asta già scaduta ? Non fare nulla } /// - /// Esegue la puntata con verifica opzionale dello stato dell'asta + /// Esegue la puntata e registra metriche /// private async Task ExecuteBid(AuctionInfo auction, AuctionState state, CancellationToken token) { try { - // Se richiesto, verifica prima che l'asta sia ancora aperta - if (auction.CheckAuctionOpenBeforeBid) - { - auction.AddLog("[PRE-CHECK] Verifica stato asta..."); - var preCheckState = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token); - - if (preCheckState == null) - { - auction.AddLog("[PRE-CHECK] FALLITO: Nessuna risposta"); - return; - } - - if (preCheckState.Status != AuctionStatus.Running) - { - auction.AddLog($"[PRE-CHECK] ABORTITO: Asta non running (status: {preCheckState.Status})"); - return; - } - - auction.AddLog($"[PRE-CHECK] OK - Timer: {preCheckState.Timer:F3}s"); - } - - // Esegui la puntata + // Esegui la puntata immediatamente var result = await _apiClient.PlaceBidAsync(auction.AuctionId, auction.OriginalUrl); auction.LastClickAt = DateTime.UtcNow; + // Registra metriche + bool isCollision = result.Error?.Contains("timer") == true || result.Error?.Contains("scaduto") == true; + _bidStrategy.RecordBidAttempt(auction, result.Success, collision: isCollision); + + if (!result.Success && isCollision) + { + _bidStrategy.RecordTimerExpired(auction); + } + // Aggiorna dati puntate da risposta server if (result.Success) { @@ -588,8 +725,25 @@ namespace AutoBidder.Services private bool ShouldBid(AuctionInfo auction, AuctionState state) { + var settings = Utilities.SettingsManager.Load(); + + // ?? CONTROLLO ANTI-AUTOBID BIDOO (PRIORITÀ MASSIMA) + // Bidoo ha un sistema di auto-puntata che si attiva a ~2 secondi. + // Aspettiamo che il timer scenda sotto la soglia per lasciare che + // gli altri utenti con auto-puntata attiva puntino prima di noi. + // Questo ci fa risparmiare puntate perché non puntiamo "troppo presto". + if (settings.WaitForAutoBidEnabled && state.Timer > settings.WaitForAutoBidThresholdSeconds) + { + // Timer ancora sopra la soglia - aspetta che le auto-puntate si attivino + if (settings.LogAutoBidWaitSkips) + { + auction.AddLog($"[AUTOBID] Timer {state.Timer:F2}s > soglia {settings.WaitForAutoBidThresholdSeconds}s - Aspetto auto-puntate Bidoo"); + } + return false; + } + // ?? CONTROLLO 0: Verifica convenienza (se dati disponibili) - // ?? IMPORTANTE: Applica solo se BuyNowPrice è valido (> 0) + // IMPORTANTE: Applica solo se BuyNowPrice è valido (> 0) // Se BuyNowPrice == 0, significa errore scraping - non bloccare le puntate if (auction.BuyNowPrice.HasValue && auction.BuyNowPrice.Value > 0 && @@ -607,8 +761,40 @@ namespace AutoBidder.Services } } - // ??? CONTROLLO 1: Limite minimo puntate residue - var settings = Utilities.SettingsManager.Load(); + // ?? CONTROLLO ANTI-COLLISIONE: Rileva aste troppo "affollate" + // Se negli ultimi 10 secondi ci sono state 3+ puntate di utenti diversi, evita + var recentBidsThreshold = 10; // secondi + var maxActiveBidders = 3; // se 3+ bidder attivi, potrebbe essere troppo affollata + + try + { + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var recentBids = auction.RecentBids + .Where(b => now - b.Timestamp <= recentBidsThreshold) + .ToList(); + + var activeBidders = recentBids + .Select(b => b.Username) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Count(); + + if (activeBidders >= maxActiveBidders) + { + // Controlla se l'ultimo bidder sono io - se sì, posso continuare + var session = _apiClient.GetSession(); + var lastBid = recentBids.OrderByDescending(b => b.Timestamp).FirstOrDefault(); + + if (lastBid != null && + !lastBid.Username.Equals(session?.Username, StringComparison.OrdinalIgnoreCase)) + { + auction.AddLog($"[COMPETITION] Asta affollata: {activeBidders} bidder attivi negli ultimi {recentBidsThreshold}s - SKIP"); + return false; + } + } + } + catch { /* Ignora errori nel controllo competizione */ } + + // ?? CONTROLLO 1: Limite minimo puntate residue if (settings.MinimumRemainingBids > 0) { var session = _apiClient.GetSession(); @@ -749,61 +935,67 @@ namespace AutoBidder.Services { // Carica impostazioni per limite massimo var settings = Utilities.SettingsManager.Load(); - var maxEntries = settings?.MaxBidHistoryEntries ?? 20; + var maxEntries = settings?.MaxBidHistoryEntries ?? 50; // Default aumentato a 50 - // Se la lista esistente è vuota, semplicemente copia le nuove - if (auction.RecentBids.Count == 0) + // ?? FIX: Usa lock per thread-safety + lock (auction.RecentBids) { - auction.RecentBids = newBids.ToList(); - - // Ordina per timestamp DECRESCENTE (più recenti in cima) - auction.RecentBids = auction.RecentBids - .OrderByDescending(b => b.Timestamp) - .ToList(); - - // Limita se necessario - if (maxEntries > 0 && auction.RecentBids.Count > maxEntries) + // Se la lista esistente è vuota, semplicemente copia le nuove + if (auction.RecentBids.Count == 0) { + auction.RecentBids = newBids.ToList(); + + // ?? FIX: Ordina per timestamp DESC, poi per prezzo DESC (per puntate stesso secondo) auction.RecentBids = auction.RecentBids - .Take(maxEntries) + .OrderByDescending(b => b.Timestamp) + .ThenByDescending(b => b.Price) .ToList(); + + // Limita se necessario + if (maxEntries > 0 && auction.RecentBids.Count > maxEntries) + { + auction.RecentBids = auction.RecentBids + .Take(maxEntries) + .ToList(); + } + + // Aggiorna statistiche bidder basandosi su RecentBids + UpdateBidderStatsFromRecentBids(auction); + return; } - // Aggiorna statistiche bidder basandosi su RecentBids - UpdateBidderStatsFromRecentBids(auction); - return; - } - - // Crea un HashSet delle puntate esistenti per ricerca veloce - // Usiamo una chiave composta: timestamp + username + price per identificare univocamente una puntata - var existingBidsKeys = new HashSet( - auction.RecentBids.Select(b => $"{b.Timestamp}_{b.Username}_{b.Price:F2}") - ); - - // Aggiungi solo le puntate nuove (non duplicate) - var bidsToAdd = newBids - .Where(b => !existingBidsKeys.Contains($"{b.Timestamp}_{b.Username}_{b.Price:F2}")) - .ToList(); - - if (bidsToAdd.Count > 0) - { - auction.RecentBids.AddRange(bidsToAdd); + // Crea un HashSet delle puntate esistenti per ricerca veloce + // Usiamo una chiave composta: timestamp + username + price per identificare univocamente una puntata + var existingBidsKeys = new HashSet( + auction.RecentBids.Select(b => $"{b.Timestamp}_{b.Username}_{b.Price:F2}") + ); - // Ordina per timestamp DECRESCENTE (più recenti in cima) - auction.RecentBids = auction.RecentBids - .OrderByDescending(b => b.Timestamp) + // Aggiungi solo le puntate nuove (non duplicate) + var bidsToAdd = newBids + .Where(b => !existingBidsKeys.Contains($"{b.Timestamp}_{b.Username}_{b.Price:F2}")) .ToList(); - // Limita al numero massimo di puntate (mantieni le più recenti = prime della lista) - if (maxEntries > 0 && auction.RecentBids.Count > maxEntries) + if (bidsToAdd.Count > 0) { + auction.RecentBids.AddRange(bidsToAdd); + + // ?? FIX: Ordina per timestamp DESC, poi per prezzo DESC (per puntate stesso secondo) auction.RecentBids = auction.RecentBids - .Take(maxEntries) + .OrderByDescending(b => b.Timestamp) + .ThenByDescending(b => b.Price) .ToList(); + + // Limita al numero massimo di puntate (mantieni le più recenti = prime della lista) + if (maxEntries > 0 && auction.RecentBids.Count > maxEntries) + { + auction.RecentBids = auction.RecentBids + .Take(maxEntries) + .ToList(); + } + + // Aggiorna statistiche bidder basandosi su RecentBids + UpdateBidderStatsFromRecentBids(auction); } - - // Aggiorna statistiche bidder basandosi su RecentBids - UpdateBidderStatsFromRecentBids(auction); } } catch (Exception ex) diff --git a/Mimante/Services/BidStrategyService.cs b/Mimante/Services/BidStrategyService.cs new file mode 100644 index 0000000..677e248 --- /dev/null +++ b/Mimante/Services/BidStrategyService.cs @@ -0,0 +1,501 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoBidder.Models; +using AutoBidder.Utilities; + +namespace AutoBidder.Services +{ + /// + /// Servizio per strategie avanzate di puntata. + /// Implementa: adaptive latency, jitter, dynamic offset, heat metric, + /// competition detection, soft retreat, probabilistic bidding, opponent profiling. + /// + public class BidStrategyService + { + private readonly Random _random = new(); + private int _sessionTotalBids = 0; + private DateTime _sessionStartedAt = DateTime.UtcNow; + + /// + /// Calcola l'offset ottimale per una puntata considerando tutti i fattori + /// + public BidTimingResult CalculateOptimalTiming(AuctionInfo auction, AppSettings settings) + { + var result = new BidTimingResult + { + BaseOffsetMs = auction.BidBeforeDeadlineMs, + FinalOffsetMs = auction.BidBeforeDeadlineMs, + ShouldBid = true + }; + + // 1. Adaptive Latency Compensation + if (settings.AdaptiveLatencyEnabled) + { + result.LatencyCompensationMs = (int)auction.AverageLatencyMs; + result.FinalOffsetMs += result.LatencyCompensationMs; + } + + // 2. Dynamic Offset (basato su heat, storico, volatilità) + if (settings.DynamicOffsetEnabled) + { + var dynamicAdjustment = CalculateDynamicOffset(auction, settings); + result.DynamicAdjustmentMs = dynamicAdjustment; + result.FinalOffsetMs += dynamicAdjustment; + } + + // 3. Jitter (randomizzazione) + if (settings.JitterEnabled || (auction.JitterEnabledOverride ?? settings.JitterEnabled)) + { + result.JitterMs = _random.Next(-settings.JitterRangeMs, settings.JitterRangeMs + 1); + result.FinalOffsetMs += result.JitterMs; + } + + // 4. Clamp ai limiti + result.FinalOffsetMs = Math.Clamp(result.FinalOffsetMs, settings.MinimumOffsetMs, settings.MaximumOffsetMs); + + // Salva offset calcolato nell'asta + auction.DynamicOffsetMs = result.FinalOffsetMs; + + return result; + } + + /// + /// Calcola offset dinamico basato su heat, storico e volatilità + /// + private int CalculateDynamicOffset(AuctionInfo auction, AppSettings settings) + { + int adjustment = 0; + + // Più l'asta è "calda", più anticipo serve + if (auction.HeatMetric > 50) + { + adjustment += (auction.HeatMetric - 50) / 2; // +0-25ms per heat 50-100 + } + + // Se ci sono state collisioni recenti, anticipa di più + if (auction.ConsecutiveCollisions > 0) + { + adjustment += auction.ConsecutiveCollisions * 10; // +10ms per ogni collisione + } + + // Se la latenza è volatile (alta deviazione), aggiungi margine + if (auction.LatencyHistory.Count >= 5) + { + var avg = auction.LatencyHistory.Average(); + var variance = auction.LatencyHistory.Sum(x => Math.Pow(x - avg, 2)) / auction.LatencyHistory.Count; + var stdDev = Math.Sqrt(variance); + + if (stdDev > 20) // Alta variabilità + { + adjustment += (int)(stdDev / 2); + } + } + + return adjustment; + } + + /// + /// Aggiorna heat metric per un'asta + /// + public void UpdateHeatMetric(AuctionInfo auction, AppSettings settings, string currentUsername = "") + { + if (!settings.CompetitionDetectionEnabled) return; + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var windowStart = now - settings.CompetitionWindowSeconds; + + // Conta bidder unici nella finestra temporale (escludo me stesso) + var recentBids = auction.RecentBids + .Where(b => b.Timestamp >= windowStart) + .Where(b => !b.Username.Equals(currentUsername, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + auction.ActiveBiddersCount = recentBids + .Select(b => b.Username) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Count(); + + // Conta collisioni (puntate nello stesso secondo) + var bidsBySecond = recentBids + .GroupBy(b => b.Timestamp) + .Where(g => g.Count() > 1) + .Count(); + + auction.CollisionCount = bidsBySecond; + + // Calcola heat metric (0-100) + // Fattori: bidder attivi (40%), frequenza puntate (30%), collisioni (30%) + + int bidderScore = Math.Min(auction.ActiveBiddersCount * 15, 40); // Max 40 punti + int frequencyScore = Math.Min(recentBids.Count * 3, 30); // Max 30 punti + int collisionScore = Math.Min(auction.CollisionCount * 10, 30); // Max 30 punti + + auction.HeatMetric = bidderScore + frequencyScore + collisionScore; + + // Identifica bidder aggressivi e situazioni di duello + if (settings.OpponentProfilingEnabled) + { + UpdateAggressiveBidders(auction, settings, currentUsername); + DetectDuelSituation(auction, settings, currentUsername); + } + } + + /// + /// Identifica e tracca bidder aggressivi (basato su ultime N puntate, esclude utente corrente) + /// + private void UpdateAggressiveBidders(AuctionInfo auction, AppSettings settings, string currentUsername) + { + // ?? FIX: Usa finestra scorrevole di ultime N puntate + var windowSize = settings.AggressiveBidderWindowSize > 0 ? settings.AggressiveBidderWindowSize : 30; + var recentWindow = auction.RecentBids + .Take(windowSize) + .ToList(); + + var bidCounts = recentWindow + .GroupBy(b => b.Username, StringComparer.OrdinalIgnoreCase) + .Select(g => new { Username = g.Key, Count = g.Count(), Percentage = (double)g.Count() / recentWindow.Count * 100 }) + .ToList(); + + auction.AggressiveBidders.Clear(); + + foreach (var bidder in bidCounts) + { + // ?? FIX: NON aggiungere l'utente corrente come aggressivo! + if (bidder.Username.Equals(currentUsername, StringComparison.OrdinalIgnoreCase)) + continue; + + // ?? FIX: Soglia più permissiva - usa percentuale invece di conteggio assoluto + // Un bidder è "aggressivo" se ha più del 40% delle puntate nella finestra (configurabile) + var percentageThreshold = settings.AggressiveBidderPercentageThreshold > 0 ? settings.AggressiveBidderPercentageThreshold : 40.0; + + if (bidder.Percentage >= percentageThreshold || bidder.Count >= settings.AggressiveBidderThreshold) + { + auction.AggressiveBidders.Add(bidder.Username); + } + } + } + + /// + /// Rileva situazione di "duello" (solo 2 bidder attivi che si contendono l'asta) + /// In questa situazione bisogna essere pronti perché se uno si ritira l'altro vince + /// + private void DetectDuelSituation(AuctionInfo auction, AppSettings settings, string currentUsername) + { + var windowSize = settings.DuelDetectionWindowSize > 0 ? settings.DuelDetectionWindowSize : 20; + var recentWindow = auction.RecentBids.Take(windowSize).ToList(); + + if (recentWindow.Count < 6) // Serve un minimo di puntate per rilevare un pattern + { + auction.IsDuelSituation = false; + auction.DuelOpponent = null; + return; + } + + var bidders = recentWindow + .GroupBy(b => b.Username, StringComparer.OrdinalIgnoreCase) + .Select(g => new { Username = g.Key, Count = g.Count(), Percentage = (double)g.Count() / recentWindow.Count * 100 }) + .OrderByDescending(b => b.Count) + .ToList(); + + // Duello: esattamente 2 bidder dominanti che coprono almeno l'80% delle puntate + if (bidders.Count >= 2) + { + var top2Percentage = bidders.Take(2).Sum(b => b.Percentage); + + if (top2Percentage >= 80 && bidders.Count <= 3) + { + auction.IsDuelSituation = true; + + // Trova l'avversario (chi NON sono io) + var opponent = bidders.FirstOrDefault(b => + !b.Username.Equals(currentUsername, StringComparison.OrdinalIgnoreCase)); + + auction.DuelOpponent = opponent?.Username; + + // Calcola chi sta dominando + var myStats = bidders.FirstOrDefault(b => + b.Username.Equals(currentUsername, StringComparison.OrdinalIgnoreCase)); + + auction.DuelAdvantage = myStats != null && opponent != null + ? myStats.Percentage - opponent.Percentage + : 0; + } + else + { + auction.IsDuelSituation = false; + auction.DuelOpponent = null; + auction.DuelAdvantage = 0; + } + } + else + { + auction.IsDuelSituation = false; + auction.DuelOpponent = null; + } + } + + /// + /// Verifica se è il caso di puntare considerando tutte le strategie + /// + public BidDecision ShouldPlaceBid(AuctionInfo auction, AuctionState state, AppSettings settings, string currentUsername) + { + var decision = new BidDecision { ShouldBid = true }; + + // Se le strategie avanzate sono disabilitate per questa asta, salta tutto + if (auction.AdvancedStrategiesEnabled == false) + { + return decision; + } + + // 1. Verifica soft retreat + if (settings.SoftRetreatEnabled || (auction.SoftRetreatEnabledOverride ?? settings.SoftRetreatEnabled)) + { + if (auction.IsInSoftRetreat) + { + var retreatEnd = auction.LastSoftRetreatAt?.AddSeconds(settings.SoftRetreatDurationSeconds); + if (retreatEnd > DateTime.UtcNow) + { + decision.ShouldBid = false; + decision.Reason = $"Soft retreat attivo (termina tra {(retreatEnd.Value - DateTime.UtcNow).TotalSeconds:F0}s)"; + return decision; + } + else + { + // Fine soft retreat + auction.IsInSoftRetreat = false; + auction.ConsecutiveCollisions = 0; + } + } + + // Verifica se attivare soft retreat + if (auction.ConsecutiveCollisions >= settings.SoftRetreatAfterCollisions) + { + auction.IsInSoftRetreat = true; + auction.LastSoftRetreatAt = DateTime.UtcNow; + decision.ShouldBid = false; + decision.Reason = $"Soft retreat attivato dopo {auction.ConsecutiveCollisions} collisioni"; + return decision; + } + } + + // 2. Verifica competition threshold + if (settings.CompetitionDetectionEnabled) + { + if (auction.ActiveBiddersCount >= settings.CompetitionThreshold) + { + // Controlla se l'ultimo bidder sono io - se sì, posso continuare + var lastBid = auction.RecentBids.OrderByDescending(b => b.Timestamp).FirstOrDefault(); + if (lastBid != null && !lastBid.Username.Equals(currentUsername, StringComparison.OrdinalIgnoreCase)) + { + if (settings.AutoPauseHotAuctions && auction.HeatMetric >= settings.HeatThresholdForPause) + { + decision.ShouldBid = false; + decision.Reason = $"Asta troppo calda (heat={auction.HeatMetric}%, bidder={auction.ActiveBiddersCount})"; + return decision; + } + } + } + } + + // 3. Verifica opponent profiling + if (settings.OpponentProfilingEnabled && auction.AggressiveBidders.Count > 0) + { + if (settings.AggressiveBidderAction == "Avoid") + { + decision.ShouldBid = false; + decision.Reason = $"Bidder aggressivi rilevati: {string.Join(", ", auction.AggressiveBidders.Take(3))}"; + return decision; + } + } + + // 4. Probabilistic bidding + if (settings.ProbabilisticBiddingEnabled) + { + var probability = CalculateBidProbability(auction, settings); + var roll = _random.NextDouble(); + + if (roll > probability) + { + decision.ShouldBid = false; + decision.Reason = $"Skip probabilistico (p={probability:P0}, roll={roll:P0})"; + return decision; + } + } + + // 5. Bankroll manager + if (settings.BankrollManagerEnabled) + { + var bankrollCheck = CheckBankrollLimits(auction, settings); + if (!bankrollCheck.CanBid) + { + decision.ShouldBid = false; + decision.Reason = bankrollCheck.Reason; + return decision; + } + } + + return decision; + } + + /// + /// Calcola probabilità di puntata basata su competizione e ROI + /// + private double CalculateBidProbability(AuctionInfo auction, AppSettings settings) + { + var probability = settings.BaseBidProbability; + + // Riduci probabilità per ogni bidder attivo oltre la soglia + var extraBidders = Math.Max(0, auction.ActiveBiddersCount - settings.CompetitionThreshold); + probability -= extraBidders * settings.ProbabilityReductionPerBidder; + + // Riduci per heat metric alto + if (auction.HeatMetric > 70) + { + probability -= 0.1; + } + + // Aumenta se abbiamo un buon ROI potenziale + if (auction.CalculatedValue?.Savings > 0) + { + probability += 0.1; + } + + return Math.Clamp(probability, 0.1, 1.0); + } + + /// + /// Verifica limiti bankroll + /// + private BankrollCheckResult CheckBankrollLimits(AuctionInfo auction, AppSettings settings) + { + var result = new BankrollCheckResult { CanBid = true }; + + // Limite puntate per asta + var maxPerAuction = auction.MaxBidsOverride ?? settings.MaxBidsPerAuction; + if (maxPerAuction > 0 && auction.SessionBidCount >= maxPerAuction) + { + result.CanBid = false; + result.Reason = $"Limite puntate per asta raggiunto ({auction.SessionBidCount}/{maxPerAuction})"; + return result; + } + + // Limite puntate per sessione + if (settings.MaxBidsPerSession > 0 && _sessionTotalBids >= settings.MaxBidsPerSession) + { + result.CanBid = false; + result.Reason = $"Limite puntate per sessione raggiunto ({_sessionTotalBids}/{settings.MaxBidsPerSession})"; + return result; + } + + // Budget giornaliero + if (settings.DailyBudgetEuro > 0) + { + var spent = _sessionTotalBids * settings.AverageBidCostEuro; + if (spent >= settings.DailyBudgetEuro) + { + result.CanBid = false; + result.Reason = $"Budget giornaliero esaurito (€{spent:F2}/€{settings.DailyBudgetEuro:F2})"; + return result; + } + } + + return result; + } + + /// + /// Registra una puntata effettuata (per tracking) + /// + public void RecordBidAttempt(AuctionInfo auction, bool success, bool collision = false) + { + auction.SessionBidCount++; + _sessionTotalBids++; + + if (success) + { + auction.SuccessfulBidCount++; + auction.ConsecutiveCollisions = 0; + } + else + { + auction.FailedBidCount++; + } + + if (collision) + { + auction.CollisionCount++; + auction.ConsecutiveCollisions++; + } + } + + /// + /// Registra un timer scaduto + /// + public void RecordTimerExpired(AuctionInfo auction) + { + auction.TimerExpiredCount++; + auction.ConsecutiveCollisions++; // Conta come "mancato" + } + + /// + /// Reset contatori sessione + /// + public void ResetSession() + { + _sessionTotalBids = 0; + _sessionStartedAt = DateTime.UtcNow; + } + + /// + /// Ottiene statistiche sessione corrente + /// + public SessionStats GetSessionStats() + { + return new SessionStats + { + TotalBids = _sessionTotalBids, + SessionDuration = DateTime.UtcNow - _sessionStartedAt + }; + } + } + + /// + /// Risultato calcolo timing puntata + /// + public class BidTimingResult + { + public int BaseOffsetMs { get; set; } + public int LatencyCompensationMs { get; set; } + public int DynamicAdjustmentMs { get; set; } + public int JitterMs { get; set; } + public int FinalOffsetMs { get; set; } + public bool ShouldBid { get; set; } + } + + /// + /// Decisione se puntare + /// + public class BidDecision + { + public bool ShouldBid { get; set; } + public string? Reason { get; set; } + } + + /// + /// Risultato verifica bankroll + /// + public class BankrollCheckResult + { + public bool CanBid { get; set; } + public string? Reason { get; set; } + } + + /// + /// Statistiche sessione + /// + public class SessionStats + { + public int TotalBids { get; set; } + public TimeSpan SessionDuration { get; set; } + } +} diff --git a/Mimante/Services/BidooBrowserService.cs b/Mimante/Services/BidooBrowserService.cs index 1e13150..35fd99d 100644 --- a/Mimante/Services/BidooBrowserService.cs +++ b/Mimante/Services/BidooBrowserService.cs @@ -375,7 +375,13 @@ namespace AutoBidder.Services var nameMatch = Regex.Match(html, @"]+class=""name[^""]*""[^>]*>([^<]+)", RegexOptions.IgnoreCase); if (nameMatch.Success) { - auction.Name = System.Net.WebUtility.HtmlDecode(nameMatch.Groups[1].Value.Trim()); + var name = System.Net.WebUtility.HtmlDecode(nameMatch.Groups[1].Value.Trim()); + // ?? FIX: Sostituisci entità HTML non standard + name = name + .Replace("+", "+") + .Replace("&plus;", "+") + .Replace(" + ", " & "); + auction.Name = name; } // Estrai prezzo compralo subito diff --git a/Mimante/Services/DatabaseService.cs b/Mimante/Services/DatabaseService.cs index 9850ca1..5b58dac 100644 --- a/Mimante/Services/DatabaseService.cs +++ b/Mimante/Services/DatabaseService.cs @@ -596,6 +596,142 @@ namespace AutoBidder.Services await using var cmd = conn.CreateCommand(); cmd.CommandText = sql; await cmd.ExecuteNonQueryAsync(); + }), + + new Migration(12, "Add complete auction history with metrics", async (conn) => { + var sql = @" + -- Tabella storia completa aste con tutte le metriche + CREATE TABLE IF NOT EXISTS CompleteAuctionHistory ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + AuctionId TEXT NOT NULL, + AuctionName TEXT NOT NULL, + ProductKey TEXT, + OriginalUrl TEXT, + + -- Dati finali + FinalPrice REAL NOT NULL, + BuyNowPrice REAL, + ShippingCost REAL, + TotalCost REAL, + Savings REAL, + SavingsPercentage REAL, + + -- Risultato + Won INTEGER NOT NULL DEFAULT 0, + WinnerUsername TEXT, + WinnerBidsUsed INTEGER, + + -- Metriche competizione + TotalResets INTEGER DEFAULT 0, + TotalUniqueBidders INTEGER DEFAULT 0, + MaxHeatMetric INTEGER DEFAULT 0, + AvgHeatMetric REAL DEFAULT 0, + TotalCollisions INTEGER DEFAULT 0, + + -- Mie statistiche + MyBidsUsed INTEGER DEFAULT 0, + MySuccessfulBids INTEGER DEFAULT 0, + MyFailedBids INTEGER DEFAULT 0, + MyTimerExpired INTEGER DEFAULT 0, + MyAvgLatencyMs REAL, + MyMinLatencyMs INTEGER, + MyMaxLatencyMs INTEGER, + + -- Offset e timing + AvgOffsetUsedMs REAL, + FinalOffsetUsedMs INTEGER, + + -- Bidder aggressivi rilevati (JSON array) + AggressiveBiddersJson TEXT, + + -- Timeline puntate (JSON array per grafico) + BidTimelineJson TEXT, + + -- Riepilogo puntatori (JSON) + BiddersSummaryJson TEXT, + + -- Timestamps + TrackingStartedAt TEXT, + ClosedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + ClosedAtHour INTEGER, + ClosedAtDayOfWeek INTEGER, + DurationSeconds INTEGER, + + -- Flag + IsCompleteTracking INTEGER DEFAULT 0, + + -- Metadata + CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + -- Indici per query analytics + CREATE INDEX IF NOT EXISTS idx_cah_auctionid ON CompleteAuctionHistory(AuctionId); + CREATE INDEX IF NOT EXISTS idx_cah_productkey ON CompleteAuctionHistory(ProductKey); + CREATE INDEX IF NOT EXISTS idx_cah_won ON CompleteAuctionHistory(Won); + CREATE INDEX IF NOT EXISTS idx_cah_closedat ON CompleteAuctionHistory(ClosedAt DESC); + CREATE INDEX IF NOT EXISTS idx_cah_hour ON CompleteAuctionHistory(ClosedAtHour); + CREATE INDEX IF NOT EXISTS idx_cah_complete ON CompleteAuctionHistory(IsCompleteTracking); + CREATE INDEX IF NOT EXISTS idx_cah_productkey_won ON CompleteAuctionHistory(ProductKey, Won); + CREATE INDEX IF NOT EXISTS idx_cah_heat ON CompleteAuctionHistory(MaxHeatMetric); + "; + await using var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + await cmd.ExecuteNonQueryAsync(); + }), + + new Migration(13, "Add bidder profiles table", async (conn) => { + var sql = @" + -- Tabella profili bidder (per opponent profiling) + CREATE TABLE IF NOT EXISTS BidderProfiles ( + Username TEXT PRIMARY KEY, + TotalAuctionsParticipated INTEGER DEFAULT 0, + TotalBidsPlaced INTEGER DEFAULT 0, + AvgBidsPerAuction REAL DEFAULT 0, + WinRate REAL DEFAULT 0, + IsAggressive INTEGER DEFAULT 0, + IsBot INTEGER DEFAULT 0, + AvgResponseTimeMs REAL, + PreferredHoursJson TEXT, + LastSeenAt TEXT, + Notes TEXT, + CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + UpdatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + -- Indici + CREATE INDEX IF NOT EXISTS idx_bidderprofiles_aggressive ON BidderProfiles(IsAggressive); + CREATE INDEX IF NOT EXISTS idx_bidderprofiles_bot ON BidderProfiles(IsBot); + CREATE INDEX IF NOT EXISTS idx_bidderprofiles_lastseen ON BidderProfiles(LastSeenAt DESC); + "; + await using var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + await cmd.ExecuteNonQueryAsync(); + }), + + new Migration(14, "Add session metrics table", async (conn) => { + var sql = @" + -- Tabella metriche per sessione + CREATE TABLE IF NOT EXISTS SessionMetrics ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + SessionStartedAt TEXT NOT NULL, + SessionEndedAt TEXT, + TotalBidsPlaced INTEGER DEFAULT 0, + TotalAuctionsWon INTEGER DEFAULT 0, + TotalAuctionsLost INTEGER DEFAULT 0, + TotalCollisions INTEGER DEFAULT 0, + TotalTimerExpired INTEGER DEFAULT 0, + AvgLatencyMs REAL, + BudgetUsedEuro REAL DEFAULT 0, + Notes TEXT, + CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + + -- Indice + CREATE INDEX IF NOT EXISTS idx_sessionmetrics_started ON SessionMetrics(SessionStartedAt DESC); + "; + await using var cmd = conn.CreateCommand(); + cmd.CommandText = sql; + await cmd.ExecuteNonQueryAsync(); }) }; @@ -1088,6 +1224,177 @@ namespace AutoBidder.Services ); } + /// + /// Salva storia completa di un'asta con tutte le metriche avanzate + /// Chiamato solo per aste tracciate dall'inizio + /// + public async Task SaveCompleteAuctionHistoryAsync(AuctionInfo auction, AuctionState finalState, bool won) + { + var closedAt = DateTime.UtcNow; + var productKey = ProductStatisticsService.GenerateProductKey(auction.Name); + + // Prepara JSON per dati complessi + var aggressiveBiddersJson = auction.AggressiveBidders.Count > 0 + ? System.Text.Json.JsonSerializer.Serialize(auction.AggressiveBidders.ToList()) + : null; + + var biddersSummary = auction.BidderStats + .Select(bs => new { Username = bs.Key, BidCount = bs.Value.BidCount }) + .OrderByDescending(b => b.BidCount) + .Take(20) + .ToList(); + var biddersSummaryJson = System.Text.Json.JsonSerializer.Serialize(biddersSummary); + + // Calcola durata se abbiamo il tracking start + int? durationSeconds = null; + if (auction.TrackingStartedAt.HasValue) + { + durationSeconds = (int)(closedAt - auction.TrackingStartedAt.Value).TotalSeconds; + } + + var sql = @" + INSERT INTO CompleteAuctionHistory + (AuctionId, AuctionName, ProductKey, OriginalUrl, + FinalPrice, BuyNowPrice, ShippingCost, TotalCost, Savings, SavingsPercentage, + Won, WinnerUsername, WinnerBidsUsed, + TotalResets, TotalUniqueBidders, MaxHeatMetric, AvgHeatMetric, TotalCollisions, + MyBidsUsed, MySuccessfulBids, MyFailedBids, MyTimerExpired, MyAvgLatencyMs, MyMinLatencyMs, MyMaxLatencyMs, + AvgOffsetUsedMs, FinalOffsetUsedMs, + AggressiveBiddersJson, BiddersSummaryJson, + TrackingStartedAt, ClosedAt, ClosedAtHour, ClosedAtDayOfWeek, DurationSeconds, + IsCompleteTracking) + VALUES + (@auctionId, @auctionName, @productKey, @originalUrl, + @finalPrice, @buyNowPrice, @shippingCost, @totalCost, @savings, @savingsPercentage, + @won, @winnerUsername, @winnerBidsUsed, + @totalResets, @totalUniqueBidders, @maxHeatMetric, @avgHeatMetric, @totalCollisions, + @myBidsUsed, @mySuccessfulBids, @myFailedBids, @myTimerExpired, @myAvgLatencyMs, @myMinLatencyMs, @myMaxLatencyMs, + @avgOffsetUsedMs, @finalOffsetUsedMs, + @aggressiveBiddersJson, @biddersSummaryJson, + @trackingStartedAt, @closedAt, @closedAtHour, @closedAtDayOfWeek, @durationSeconds, + @isCompleteTracking); + "; + + await ExecuteNonQueryAsync(sql, + new SqliteParameter("@auctionId", auction.AuctionId), + new SqliteParameter("@auctionName", auction.Name), + new SqliteParameter("@productKey", productKey), + new SqliteParameter("@originalUrl", auction.OriginalUrl), + new SqliteParameter("@finalPrice", finalState.Price), + new SqliteParameter("@buyNowPrice", (object?)auction.BuyNowPrice ?? DBNull.Value), + new SqliteParameter("@shippingCost", (object?)auction.ShippingCost ?? DBNull.Value), + new SqliteParameter("@totalCost", (object?)auction.CalculatedValue?.TotalCostIfWin ?? DBNull.Value), + new SqliteParameter("@savings", (object?)auction.CalculatedValue?.Savings ?? DBNull.Value), + new SqliteParameter("@savingsPercentage", (object?)auction.CalculatedValue?.SavingsPercentage ?? DBNull.Value), + new SqliteParameter("@won", won ? 1 : 0), + new SqliteParameter("@winnerUsername", (object?)finalState.LastBidder ?? DBNull.Value), + new SqliteParameter("@winnerBidsUsed", DBNull.Value), // Da calcolare se disponibile + new SqliteParameter("@totalResets", auction.ResetCount), + new SqliteParameter("@totalUniqueBidders", auction.BidderStats.Count), + new SqliteParameter("@maxHeatMetric", auction.HeatMetric), + new SqliteParameter("@avgHeatMetric", (double)auction.HeatMetric), + new SqliteParameter("@totalCollisions", auction.CollisionCount), + new SqliteParameter("@myBidsUsed", auction.SessionBidCount), + new SqliteParameter("@mySuccessfulBids", auction.SuccessfulBidCount), + new SqliteParameter("@myFailedBids", auction.FailedBidCount), + new SqliteParameter("@myTimerExpired", auction.TimerExpiredCount), + new SqliteParameter("@myAvgLatencyMs", auction.AverageLatencyMs), + new SqliteParameter("@myMinLatencyMs", auction.LatencyHistory.Count > 0 ? auction.LatencyHistory.Min() : DBNull.Value), + new SqliteParameter("@myMaxLatencyMs", auction.LatencyHistory.Count > 0 ? auction.LatencyHistory.Max() : (object)DBNull.Value), + new SqliteParameter("@avgOffsetUsedMs", (double)auction.DynamicOffsetMs), + new SqliteParameter("@finalOffsetUsedMs", auction.LastUsedOffsetMs), + new SqliteParameter("@aggressiveBiddersJson", (object?)aggressiveBiddersJson ?? DBNull.Value), + new SqliteParameter("@biddersSummaryJson", biddersSummaryJson), + new SqliteParameter("@trackingStartedAt", auction.TrackingStartedAt?.ToString("O") ?? (object)DBNull.Value), + new SqliteParameter("@closedAt", closedAt.ToString("O")), + new SqliteParameter("@closedAtHour", closedAt.Hour), + new SqliteParameter("@closedAtDayOfWeek", (int)closedAt.DayOfWeek), + new SqliteParameter("@durationSeconds", (object?)durationSeconds ?? DBNull.Value), + new SqliteParameter("@isCompleteTracking", auction.IsTrackedFromStart ? 1 : 0) + ); + + Console.WriteLine($"[DatabaseService] ✓ Salvata storia completa per {auction.Name} (complete={auction.IsTrackedFromStart})"); + } + + /// + /// Ottiene la storia completa delle aste con filtri + /// + public async Task> GetCompleteAuctionHistoryAsync( + string? productNameFilter = null, + bool? wonFilter = null, + int limit = 100, + string orderBy = "ClosedAt", + bool descending = true) + { + var results = new List(); + + var sql = $@" + SELECT Id, AuctionId, AuctionName, ProductKey, OriginalUrl, + FinalPrice, BuyNowPrice, ShippingCost, TotalCost, Savings, SavingsPercentage, + Won, WinnerUsername, WinnerBidsUsed, + TotalResets, TotalUniqueBidders, MaxHeatMetric, AvgHeatMetric, TotalCollisions, + MyBidsUsed, MySuccessfulBids, MyFailedBids, MyTimerExpired, MyAvgLatencyMs, + ClosedAt, ClosedAtHour, DurationSeconds, IsCompleteTracking, + AggressiveBiddersJson, BiddersSummaryJson + FROM CompleteAuctionHistory + WHERE 1=1 + {(productNameFilter != null ? "AND AuctionName LIKE @nameFilter" : "")} + {(wonFilter.HasValue ? "AND Won = @wonFilter" : "")} + ORDER BY {orderBy} {(descending ? "DESC" : "ASC")} + LIMIT @limit; + "; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + cmd.Parameters.AddWithValue("@limit", limit); + + if (productNameFilter != null) + cmd.Parameters.AddWithValue("@nameFilter", $"%{productNameFilter}%"); + if (wonFilter.HasValue) + cmd.Parameters.AddWithValue("@wonFilter", wonFilter.Value ? 1 : 0); + + await using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + results.Add(new CompleteAuctionHistoryRecord + { + Id = reader.GetInt32(0), + AuctionId = reader.GetString(1), + AuctionName = reader.GetString(2), + ProductKey = reader.IsDBNull(3) ? null : reader.GetString(3), + OriginalUrl = reader.IsDBNull(4) ? null : reader.GetString(4), + FinalPrice = reader.GetDouble(5), + BuyNowPrice = reader.IsDBNull(6) ? null : reader.GetDouble(6), + ShippingCost = reader.IsDBNull(7) ? null : reader.GetDouble(7), + TotalCost = reader.IsDBNull(8) ? null : reader.GetDouble(8), + Savings = reader.IsDBNull(9) ? null : reader.GetDouble(9), + SavingsPercentage = reader.IsDBNull(10) ? null : reader.GetDouble(10), + Won = reader.GetInt32(11) == 1, + WinnerUsername = reader.IsDBNull(12) ? null : reader.GetString(12), + WinnerBidsUsed = reader.IsDBNull(13) ? null : reader.GetInt32(13), + TotalResets = reader.IsDBNull(14) ? 0 : reader.GetInt32(14), + TotalUniqueBidders = reader.IsDBNull(15) ? 0 : reader.GetInt32(15), + MaxHeatMetric = reader.IsDBNull(16) ? 0 : reader.GetInt32(16), + AvgHeatMetric = reader.IsDBNull(17) ? 0 : reader.GetDouble(17), + TotalCollisions = reader.IsDBNull(18) ? 0 : reader.GetInt32(18), + MyBidsUsed = reader.IsDBNull(19) ? 0 : reader.GetInt32(19), + MySuccessfulBids = reader.IsDBNull(20) ? 0 : reader.GetInt32(20), + MyFailedBids = reader.IsDBNull(21) ? 0 : reader.GetInt32(21), + MyTimerExpired = reader.IsDBNull(22) ? 0 : reader.GetInt32(22), + MyAvgLatencyMs = reader.IsDBNull(23) ? null : reader.GetDouble(23), + ClosedAt = reader.IsDBNull(24) ? DateTime.MinValue : DateTime.Parse(reader.GetString(24)), + ClosedAtHour = reader.IsDBNull(25) ? 0 : reader.GetInt32(25), + DurationSeconds = reader.IsDBNull(26) ? null : reader.GetInt32(26), + IsCompleteTracking = reader.GetInt32(27) == 1, + AggressiveBiddersJson = reader.IsDBNull(28) ? null : reader.GetString(28), + BiddersSummaryJson = reader.IsDBNull(29) ? null : reader.GetString(29) + }); + } + + return results; + } + /// /// Aggiorna o inserisce statistiche aggregate per un prodotto /// diff --git a/Mimante/UNRAID_TEMPLATE.md b/Mimante/UNRAID_TEMPLATE.md deleted file mode 100644 index 41970f9..0000000 --- a/Mimante/UNRAID_TEMPLATE.md +++ /dev/null @@ -1,410 +0,0 @@ -# ?? GUIDA CONFIGURAZIONE UNRAID - AutoBidder v1.2.0 - -## ?? Template Container Unraid - -### Informazioni Base - -``` -Nome: AutoBidder -Descrizione: Sistema automatizzato gestione aste Bidoo -Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 -WebUI: http://[IP]:[PORT:8889] -Icon URL: (opzionale) -``` - ---- - -## ?? Configurazione Parametri - -### 1. Port Mappings - -| Nome | Container Port | Host Port | Tipo | Descrizione | -|------|---------------|-----------|------|-------------| -| **WebUI** | `8080` | `8889` | TCP | Interfaccia web AutoBidder | - -**Configurazione Unraid:** -``` -Container Port: 8080 -Host Port: 8889 -Connection Type: TCP -``` - ---- - -### 2. Volume Mappings - -| Nome | Container Path | Host Path | Modo | Descrizione | -|------|---------------|-----------|------|-------------| -| **AppData** | `/app/Data` | `/mnt/user/appdata/autobidder/data` | Read/Write | Database e configurazioni | -| **Logs** | `/app/logs` | `/mnt/user/appdata/autobidder/logs` | Read/Write | Log applicazione (opzionale) | - -**Configurazione Unraid:** -``` -Volume 1: - Container Path: /app/Data - Host Path: /mnt/user/appdata/autobidder/data - Access Mode: Read/Write - -Volume 2 (opzionale): - Container Path: /app/logs - Host Path: /mnt/user/appdata/autobidder/logs - Access Mode: Read/Write -``` - ---- - -### 3. Environment Variables (OBBLIGATORIO) - -#### ?? Autenticazione Applicazione - -| Variable | Valore | Descrizione | -|----------|--------|-------------| -| **ADMIN_USERNAME** | `admin` | Username amministratore | -| **ADMIN_PASSWORD** | `MyS3cur3P@ss!2024` | Password admin (min 12 caratteri) | - -**Requisiti password:** -- ? Minimo 12 caratteri -- ? Maiuscole + minuscole -- ? Numeri -- ? Simboli speciali - -#### ?? Sessione Bidoo - -**NON servono credenziali qui!** - -Il cookie di sessione Bidoo si configura **dall'interfaccia web**: -1. Login su AutoBidder -2. Vai su **Settings ? Sessione Bidoo** -3. Incolla il cookie di sessione ottenuto da Bidoo.it -4. Salva - -#### ?? Opzionali - -| Variable | Valore Default | Descrizione | -|----------|---------------|-------------| -| **ASPNETCORE_ENVIRONMENT** | `Production` | Ambiente ASP.NET | -| **USE_POSTGRES** | `true` | Usa PostgreSQL per stats | -| **LOG_LEVEL** | `Information` | Livello logging | - ---- - -## ?? Template Completo Unraid - -### XML Template (my-AutoBidder.xml) - -```xml - - - AutoBidder - gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 - https://gitea.encke-hake.ts.net/ - bridge - - sh - false - https://gitea.encke-hake.ts.net/Alby96/Mimante - https://gitea.encke-hake.ts.net/Alby96/Mimante - Sistema Blazor .NET 8 per monitoraggio e partecipazione automatica aste Bidoo - Tools: - http://[IP]:[PORT:8889] - - https://raw.githubusercontent.com/selfhosters/unRAID-CA-templates/master/templates/img/bidoo.png - - - - - - - - - 8889 - - /mnt/user/appdata/autobidder/data - - /mnt/user/appdata/autobidder/logs - - admin - - - - Production - - true - - Information - -``` - ---- - -## ?? Installazione Step-by-Step - -### Step 1: Aggiungi Container - -1. Unraid WebUI ? **Docker** ? **Add Container** -2. Click: **Advanced View** (top right) - -### Step 2: Configurazione Base - -``` -Name: AutoBidder -Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 -Network Type: Bridge -Console shell command: Shell -``` - -### Step 3: Port Mappings - -``` -Container Port: 8080 -Host Port: 8889 -Protocol: TCP -``` - -### Step 4: Path Mappings - -``` -Container Path: /app/Data -Host Path: /mnt/user/appdata/autobidder/data -Access Mode: Read/Write -``` - -### Step 5: Environment Variables - -**OBBLIGATORIO - Autenticazione:** -``` -Key: ADMIN_USERNAME -Value: admin - -Key: ADMIN_PASSWORD -Value: TuaPasswordSicura123! -``` - -**Sessione Bidoo:** -``` -NON configurare qui! -Si imposta dall'interfaccia web dopo il login. -``` - -**Opzionali:** -``` -Key: ASPNETCORE_ENVIRONMENT -Value: Production - -Key: USE_POSTGRES -Value: true - -Key: LOG_LEVEL -Value: Information -``` - -### Step 6: Apply e Start - -1. Click **Apply** -2. Unraid scaricherà l'immagine -3. Container si avvierà automaticamente - ---- - -## ? Verifica Installazione - -### 1. Controlla Log - -``` -Unraid ? Docker ? AutoBidder ? Log -``` - -**Log attesi:** -``` -[Identity] Database initialized -[Identity] Admin user created: admin -[DB] Database initialized successfully -[Kestrel] Listening on: http://+:8080 -Application started -``` - -### 2. Test WebUI - -``` -Browser: http://192.168.30.23:8889 -``` - -Dovresti vedere: -- ? Redirect automatico a `/login` -- ? Pagina login AutoBidder - -### 3. Primo Login - -``` -Username: admin -Password: (valore ADMIN_PASSWORD) -``` - -Dopo login: -- ? Homepage AutoBidder -- ? Monitoring aste attivo - ---- - -## ?? Troubleshooting - -### Problema: Container non parte - -**Verifica log:** -``` -Unraid ? Docker ? AutoBidder ? Log -``` - -**Cause comuni:** -- ? `ADMIN_PASSWORD` non configurata -- ? `BIDOO_USERNAME` o `BIDOO_PASSWORD` mancanti -- ? Port 8889 già in uso - -**Soluzione:** -1. Stop container -2. Edit container -3. Verifica environment variables -4. Start container - -### Problema: "Account temporaneamente bloccato" - -**Causa:** 5 tentativi login falliti - -**Soluzione:** -- Aspetta 15 minuti (lockout automatico) -- Verifica password configurata - -### Problema: Pagina non carica - -**Verifica:** -1. Container è "Started" (Unraid Docker) -2. Port 8889 corretto -3. IP Unraid corretto - -**Test:** -```bash -# SSH su Unraid -curl http://localhost:8889 -``` - -### Problema: Bidoo non si connette - -**Verifica:** -1. `BIDOO_USERNAME` e `BIDOO_PASSWORD` corretti -2. Account Bidoo attivo -3. Log container per errori connessione - -**Log:** -``` -Unraid ? Docker ? AutoBidder ? Log -Cerca: [Bidoo] o [Session] -``` - ---- - -## ?? Aggiornamento Versione - -### Da v1.1.x a v1.2.0 - -1. **Stop container:** - ``` - Unraid ? Docker ? AutoBidder ? Stop - ``` - -2. **Edit container:** - ``` - Unraid ? Docker ? AutoBidder ? Edit - ``` - -3. **Aggiorna repository:** - ``` - Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 - ``` - -4. **Aggiungi nuove env vars:** - ``` - ADMIN_USERNAME=admin - ADMIN_PASSWORD=TuaPasswordSicura123! - BIDOO_USERNAME=email@bidoo.com - BIDOO_PASSWORD=bidoo_pass - ``` - -5. **Apply e Start** - -6. **Verifica log** (primo avvio) - ---- - -## ?? Checklist Configurazione - -Prima di avviare container: - -- [ ] Repository corretto (`1.2.0`) -- [ ] Port mapping: `8889:8080` -- [ ] Volume: `/app/Data` ? `/mnt/user/appdata/autobidder/data` -- [ ] `ADMIN_USERNAME` configurato -- [ ] `ADMIN_PASSWORD` configurata (min 12 caratteri) -- [ ] `BIDOO_USERNAME` configurato -- [ ] `BIDOO_PASSWORD` configurata -- [ ] WebUI accessibile da browser - -Dopo avvio: - -- [ ] Log non mostra errori -- [ ] Login funzionante -- [ ] Homepage AutoBidder carica -- [ ] Connessione Bidoo OK - ---- - -## ?? Esempio Configurazione Completa - -``` -=== CONTAINER SETTINGS === -Name: AutoBidder -Repository: gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 -Network: bridge - -=== PORT MAPPINGS === -8080 (container) ? 8889 (host) [TCP] - -=== VOLUME MAPPINGS === -/app/Data ? /mnt/user/appdata/autobidder/data [RW] -/app/logs ? /mnt/user/appdata/autobidder/logs [RW] - -=== ENVIRONMENT VARIABLES === -ADMIN_USERNAME=admin -ADMIN_PASSWORD=MyS3cur3P@ssw0rd!2024 -ASPNETCORE_ENVIRONMENT=Production -USE_POSTGRES=true -LOG_LEVEL=Information - -=== SESSIONE BIDOO === -Configurata dall'interfaccia web: -Settings ? Sessione Bidoo ? Incolla cookie - -=== ACCESS === -WebUI: http://192.168.30.23:8889 -Login: admin / MyS3cur3P@ssw0rd!2024 -``` - ---- - -## ?? Supporto - -**Documentazione:** -- [SECURITY.md](../SECURITY.md) - Guida sicurezza -- [README.md](../README.md) - Overview progetto -- [CHANGELOG.md](../CHANGELOG.md) - Note versioni - -**Log dettagliati:** -``` -Unraid ? Docker ? AutoBidder ? Log -``` - -**Issues:** -https://gitea.encke-hake.ts.net/Alby96/Mimante/issues - ---- - -**?? AutoBidder v1.2.0 - Pronto per Unraid con autenticazione sicura!** diff --git a/Mimante/Utilities/SettingsManager.cs b/Mimante/Utilities/SettingsManager.cs index c91419e..16a00d4 100644 --- a/Mimante/Utilities/SettingsManager.cs +++ b/Mimante/Utilities/SettingsManager.cs @@ -99,6 +99,250 @@ namespace AutoBidder.Utilities /// Default: 180 (6 mesi), 0 = disabilitato /// public int DatabaseMaxRetentionDays { get; set; } = 180; + + // ??????????????????????????????????????????????????????????????? + // STRATEGIE AVANZATE DI PUNTATA + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita compensazione adattiva della latenza. + /// Misura latenza reale per ogni asta e adatta l'anticipo automaticamente. + /// Default: true + /// + public bool AdaptiveLatencyEnabled { get; set; } = true; + + /// + /// Abilita jitter casuale sull'offset per evitare sincronizzazione con altri bot. + /// Aggiunge ±JitterRangeMs al timing di puntata. + /// Default: true + /// + public bool JitterEnabled { get; set; } = true; + + /// + /// Range massimo del jitter casuale in millisecondi (±X ms). + /// Default: 50 (range -50ms a +50ms) + /// + public int JitterRangeMs { get; set; } = 50; + + /// + /// Abilita offset dinamico per asta basato su ping, storico e volatilità. + /// Default: true + /// + public bool DynamicOffsetEnabled { get; set; } = true; + + /// + /// Offset minimo garantito in ms (non scende mai sotto questo valore). + /// Default: 80 + /// + public int MinimumOffsetMs { get; set; } = 80; + + /// + /// Offset massimo in ms (non supera mai questo valore). + /// Default: 500 + /// + public int MaximumOffsetMs { get; set; } = 500; + + // ?? STRATEGIA ANTI-AUTOBID BIDOO + // Bidoo ha un sistema di auto-puntata integrato che si attiva a ~2 secondi. + // Aspettando che questa soglia venga superata, lasciamo che gli altri + // utenti con auto-puntata attiva puntino prima di noi, risparmiando puntate. + + /// + /// Abilita la strategia di attesa per le auto-puntate di Bidoo. + /// Se true, aspetta che il timer scenda sotto la soglia prima di puntare. + /// Default: true + /// + public bool WaitForAutoBidEnabled { get; set; } = true; + + /// + /// Soglia in secondi sotto la quale si può puntare. + /// Bidoo attiva le auto-puntate a ~2 secondi, quindi aspettiamo che passino. + /// Default: 1.8 (punta solo quando timer < 1.8s, dopo che le auto-puntate si sono attivate) + /// + public double WaitForAutoBidThresholdSeconds { get; set; } = 1.8; + + /// + /// Se true, logga quando salta una puntata per aspettare le auto-puntate. + /// Default: false (per evitare spam nel log) + /// + public bool LogAutoBidWaitSkips { get; set; } = false; + + // ?????????????????????????????????????????????????????????????? + // RILEVAMENTO COMPETIZIONE E HEAT METRIC + // ?????????????????????????????????????????????????????????????? + + /// + /// Abilita rilevamento competizione e heat metric. + /// Conta bidder attivi e collisioni per determinare il "calore" dell'asta. + /// Default: true + /// + public bool CompetitionDetectionEnabled { get; set; } = true; + + /// + /// Finestra temporale in secondi per contare bidder attivi. + /// Default: 30 (ultimi 30 secondi) + /// + public int CompetitionWindowSeconds { get; set; } = 30; + + /// + /// Numero minimo di bidder attivi per considerare l'asta "affollata". + /// Se >= a questa soglia, applica logica di evitamento. + /// Default: 3 + /// + public int CompetitionThreshold { get; set; } = 3; + + /// + /// Abilita auto-pausa per aste troppo competitive. + /// Default: false (solo warning, non pausa automatica) + /// + public bool AutoPauseHotAuctions { get; set; } = false; + + /// + /// Soglia heat metric per auto-pausa (0-100). + /// Default: 80 (pausa se heat >= 80%) + /// + public int HeatThresholdForPause { get; set; } = 80; + + // ??????????????????????????????????????????????????????????????? + // SOFT RETREAT E COLLISION MANAGEMENT + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita soft retreat automatico dopo N collisioni consecutive. + /// Default: true + /// + public bool SoftRetreatEnabled { get; set; } = true; + + /// + /// Numero di collisioni consecutive per attivare soft retreat. + /// Default: 3 + /// + public int SoftRetreatAfterCollisions { get; set; } = 3; + + /// + /// Durata pausa soft retreat in secondi. + /// Default: 30 + /// + public int SoftRetreatDurationSeconds { get; set; } = 30; + + // ??????????????????????????????????????????????????????????????? + // PROBABILISTIC BIDDING + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita policy di puntata probabilistica. + /// Decide se puntare con probabilità p basata su competizione e ROI. + /// Default: false (richiede tuning) + /// + public bool ProbabilisticBiddingEnabled { get; set; } = false; + + /// + /// Probabilità base di puntata (0.0 - 1.0). + /// Default: 0.8 (80%) + /// + public double BaseBidProbability { get; set; } = 0.8; + + /// + /// Fattore di riduzione probabilità per ogni bidder attivo extra. + /// Default: 0.1 (riduce del 10% per ogni bidder oltre la soglia) + /// + public double ProbabilityReductionPerBidder { get; set; } = 0.1; + + // ??????????????????????????????????????????????????????????????? + // OPPONENT PROFILING + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita profiling degli avversari. + /// Identifica utenti aggressivi e applica regole specifiche. + /// Default: true + /// + public bool OpponentProfilingEnabled { get; set; } = true; + + /// + /// Soglia puntate per considerare un utente "aggressivo". + /// Default: 10 (se un utente ha fatto >= 10 puntate in un'asta) + /// + public int AggressiveBidderThreshold { get; set; } = 10; + + /// + /// Dimensione finestra scorrevole per analisi bidder aggressivi. + /// Analizza le ultime N puntate invece del conteggio totale. + /// Default: 30 (ultime 30 puntate) + /// + public int AggressiveBidderWindowSize { get; set; } = 30; + + /// + /// Soglia percentuale per considerare un utente "aggressivo". + /// Se un utente ha più di X% delle puntate nella finestra, è aggressivo. + /// Default: 40 (40% delle puntate) + /// + public double AggressiveBidderPercentageThreshold { get; set; } = 40.0; + + /// + /// Dimensione finestra per rilevamento situazioni di duello. + /// Default: 20 (ultime 20 puntate) + /// + public int DuelDetectionWindowSize { get; set; } = 20; + + /// + /// Azione da intraprendere con bidder aggressivi. + /// "Avoid" = evita l'asta, "Compete" = continua normalmente, "Outbid" = punta più aggressivamente + /// Default: "Compete" (cambiato da Avoid per essere meno restrittivo) + /// + public string AggressiveBidderAction { get; set; } = "Compete"; + + // ??????????????????????????????????????????????????????????????? + // BANKROLL & SAFETY MANAGER + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita gestione bankroll per limitare spese. + /// Default: true + /// + public bool BankrollManagerEnabled { get; set; } = true; + + /// + /// Limite massimo puntate per sessione (0 = illimitato). + /// Default: 0 + /// + public int MaxBidsPerSession { get; set; } = 0; + + /// + /// Limite massimo puntate per singola asta (0 = illimitato). + /// Default: 0 + /// + public int MaxBidsPerAuction { get; set; } = 0; + + /// + /// Budget massimo giornaliero in euro (0 = illimitato). + /// Calcolato come: puntate usate × costo medio puntata. + /// Default: 0 + /// + public double DailyBudgetEuro { get; set; } = 0; + + /// + /// Costo medio per puntata in euro (per calcolo budget). + /// Default: 0.15 + /// + public double AverageBidCostEuro { get; set; } = 0.15; + + // ??????????????????????????????????????????????????????????????? + // LOGGING AVANZATO + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita logging avanzato con metriche dettagliate. + /// Include: collisioni, timer scaduto, latenza, heat metric. + /// Default: true + /// + public bool AdvancedLoggingEnabled { get; set; } = true; + + /// + /// Salva metriche per ogni puntata nel database. + /// Default: true + /// + public bool SaveBidMetricsToDatabase { get; set; } = true; } public static class SettingsManager diff --git a/Mimante/VERIFICA_CONFIGURAZIONE_GITEA.md b/Mimante/VERIFICA_CONFIGURAZIONE_GITEA.md deleted file mode 100644 index 0eaaf7c..0000000 --- a/Mimante/VERIFICA_CONFIGURAZIONE_GITEA.md +++ /dev/null @@ -1,183 +0,0 @@ -# ? Verifica Configurazione Docker + Gitea (2026) - -## ?? Checklist Completa secondo Guida Gitea - -### ? 1. Preparazione su Gitea - -| Requisito | Stato | Dettagli | -|-----------|-------|----------| -| Container Registry abilitato | ? CONFERMATO | Package esistente su `https://gitea.encke-hake.ts.net/Alby96/-/packages` | -| Token PAT generato | ?? DA VERIFICARE | Deve avere permessi `read:packages` + `write:packages` | -| Token usato per login | ?? DA FARE | `docker login gitea.encke-hake.ts.net` con Token come password | - -**?? AZIONE RICHIESTA:** -```bash -# Genera token su: https://gitea.encke-hake.ts.net/user/settings/applications -# Scope necessari: read:packages, write:packages - -# Poi autentica Docker: -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [TOKEN_PAT_GENERATO] -``` - ---- - -### ? 2. Configurazione Progetto Visual Studio - -| Requisito | Stato | Dettagli | -|-----------|-------|----------| -| Supporto Docker abilitato | ? OK | `DockerDefaultTargetOS=Linux`, `DockerfileFile=Dockerfile` | -| Dockerfile presente | ? OK | Valido, espone porta 8080, healthcheck configurato | -| .dockerignore presente | ? OK | Esclude file non necessari | -| Profili pubblicazione | ? OK | `GiteaRegistry.pubxml` e `GiteaRegistry-Versioned.pubxml` | - ---- - -### ?? 3. Convenzione Nomi Docker (CORRETTO) - -**? PROBLEMA RILEVATO E CORRETTO:** - -**Prima (ERRATO - 4 livelli):** -``` -gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest -??????????????????????? ?????? ??????? ??????????? - registro owner ??? ??? immagine -``` - -**Dopo correzione (CORRETTO - 3 livelli):** -``` -gitea.encke-hake.ts.net/alby96/autobidder:latest -??????????????????????? ?????? ??????????? - registro owner immagine -``` - -**?? Convenzione Gitea ufficiale:** -``` -Sintassi: {registro}/{proprietario}/{immagine}:{tag} -Esempio: gitea.example.com/mio-utente/mia-app:latest -``` - -**? MODIFICHE APPLICATE:** -- `AutoBidder.csproj`: `gitea.encke-hake.ts.net/alby96` -- `GiteaRegistry.pubxml`: Aggiornato con path corretto -- `DOCKER_PUBLISH_GUIDE.md`: Tutti i comandi aggiornati - ---- - -### ? 4. File Modificati - -#### `AutoBidder.csproj` -```xml - -autobidder -$(Version) - -gitea.encke-hake.ts.net/alby96 -``` - -#### `Properties/PublishProfiles/GiteaRegistry.pubxml` -```xml - -gitea.encke-hake.ts.net/alby96 -autobidder -``` - -#### `Dockerfile` -? **Nessuna modifica necessaria** - Il Dockerfile è corretto: -- Build multi-stage (sdk ? publish ? runtime) -- Porta 8080 esposta -- Healthcheck configurato -- Labels OCI -- Variabili ambiente corrette - ---- - -## ?? Procedura di Test - -### 1. Autenticazione -```bash -docker logout gitea.encke-hake.ts.net -docker login gitea.encke-hake.ts.net -# Username: Alby96 -# Password: [TOKEN_PAT] -``` - -### 2. Build con Convenzione Corretta -```bash -# Rebuild completo senza cache -docker build --no-cache \ - -t gitea.encke-hake.ts.net/alby96/autobidder:latest \ - -t gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 \ - . -``` - -### 3. Push su Gitea -```bash -docker push gitea.encke-hake.ts.net/alby96/autobidder:latest -docker push gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 -``` - -### 4. Verifica su Gitea -``` -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder/latest -``` - -Dovresti vedere: -- Nome package: `autobidder` (NON più `mimante/autobidder`) -- Tag disponibili: `latest`, `1.0.0` -- Data aggiornata ad oggi -- Digest SHA256 nuovo - ---- - -## ?? Confronto Prima/Dopo - -| Aspetto | Prima (Errato) | Dopo (Corretto) | -|---------|----------------|-----------------| -| **Path Registry** | `gitea.encke-hake.ts.net/alby96/mimante` | `gitea.encke-hake.ts.net/alby96` | -| **Immagine Completa** | `gitea.encke-hake.ts.net/alby96/mimante/autobidder:latest` | `gitea.encke-hake.ts.net/alby96/autobidder:latest` | -| **Package su Gitea** | `mimante/autobidder` | `autobidder` | -| **Link Gitea** | `.../container/mimante%2Fautobidder/...` | `.../container/autobidder/...` | -| **Livelli Path** | 4 (errato) | 3 (corretto) | -| **Conforme Guida Gitea** | ? NO | ? SÌ | - ---- - -## ?? Possibili Problemi e Soluzioni - -### Problema 1: Package vecchio ancora visibile -**Soluzione:** Il vecchio package `mimante/autobidder` continuerà ad esistere. Puoi: -- Eliminarlo manualmente da Gitea (Settings del package) -- Oppure lasciarlo (non interferisce con il nuovo) - -### Problema 2: Autenticazione fallita -**Soluzione:** -- Usa Token PAT invece della password -- Verifica scope del token: `read:packages`, `write:packages` -- Se hai 2FA attivo, il Token è OBBLIGATORIO - -### Problema 3: SSL/TLS Errors -**Soluzione:** Se Gitea usa certificati self-signed: -```bash -# Aggiungi a Docker daemon.json -{ - "insecure-registries": ["gitea.encke-hake.ts.net"] -} -``` - ---- - -## ? Configurazione Finale Verificata - -**Tutti i requisiti soddisfatti:** -- ? Container Registry Gitea abilitato -- ? Dockerfile corretto e ottimizzato -- ? Convenzione nomi corretta (3 livelli) -- ? Profili di pubblicazione aggiornati -- ? Supporto Docker in Visual Studio -- ? Build multi-stage funzionante -- ? Healthcheck configurato -- ?? Token PAT da generare/verificare - -**Prossimo step:** Genera Token PAT e testa il push! diff --git a/Mimante/VERSIONING.md b/Mimante/VERSIONING.md deleted file mode 100644 index 994a757..0000000 --- a/Mimante/VERSIONING.md +++ /dev/null @@ -1,305 +0,0 @@ -# ?? Sistema di Versionamento Automatico - -## ?? Strategia Versioning - -Il progetto AutoBidder segue **[Semantic Versioning 2.0.0](https://semver.org/)** nel formato: - -``` -MAJOR.MINOR.PATCH -``` - -### Quando Incrementare - -| Tipo | Quando | Esempio | -|------|--------|---------| -| **MAJOR** | Breaking changes | `1.5.2` ? `2.0.0` | -| **MINOR** | Nuove feature retrocompatibili | `1.5.2` ? `1.6.0` | -| **PATCH** | Bug fix retrocompatibili | `1.5.2` ? `1.5.3` | - ---- - -## ?? Workflow di Rilascio - -### 1. Modifica Versione in `.csproj` - -```xml - - - - 1.1.0 - 1.1.0.0 - 1.1.0.0 - 1.1.0 - -``` - -**? Questa è la FONTE UNICA della versione!** - -### 2. Aggiorna `Dockerfile` Labels - -```dockerfile -LABEL org.opencontainers.image.version="1.1.0" -``` - -### 3. Documenta in `CHANGELOG.md` - -```markdown -## [1.1.0] - 2025-01-18 - -### ? Aggiunte -- Pubblicazione automatica su Gitea -- ... - -### ?? Modifiche -- Porta HTTP: 5000 ? 8080 -- ... -``` - -### 4. Pubblica su Gitea - -```bash -# Da Visual Studio: Tasto destro ? Pubblica ? GiteaRegistry -# Oppure da CLI: -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -**Risultato automatico:** -- `gitea.../autobidder:latest` (aggiornato) -- `gitea.../autobidder:1.1.0` (nuovo tag) - ---- - -## ?? Storico Versioni - -### v1.1.0 - Docker/Gitea Publishing Workflow (2025-01-18) - -**Feature Principali:** -- ? Pubblicazione automatica Gitea Container Registry -- ? Versionamento automatico da `.csproj` -- ?? HTTPS disabilitato di default in container -- ?? Porta HTTP standardizzata (8080) -- ?? Fix errore Visual Studio "ContainerBuild" -- ?? Fix crash container certificati HTTPS - -**Breaking Changes:** -- ?? Porta: `5000` ? `8080` -- ?? Path Gitea: `alby96/mimante/autobidder` ? `alby96/autobidder` -- ?? HTTPS: abilitato ? disabilitato (opzionale) - -**Migrazione:** -```bash -# Aggiorna port mapping -docker run -p 5000:8080 ... # era 5000:5000 - -# Pull nuova convenzione path -docker pull gitea.../alby96/autobidder:1.1.0 -``` - -### v1.0.0 - Release Iniziale (2025-01-17) - -**Feature Principali:** -- ? Sistema AutoBidder Blazor .NET 8 -- ? Monitoraggio aste Bidoo -- ? Offerte automatiche -- ? Statistiche PostgreSQL -- ? Docker support base - ---- - -## ?? Esempi Pratici - -### Scenario 1: Bug Fix - -**Situazione:** Corretto bug calcolo statistiche - -```xml - -1.1.0 - - -1.1.1 -``` - -```markdown -## [1.1.1] - 2025-01-19 - -### ?? Correzioni -- Fix calcolo media offerte in Statistics.razor -``` - -### Scenario 2: Nuova Feature - -**Situazione:** Aggiunto supporto notifiche email - -```xml - -1.1.1 - - -1.2.0 -``` - -```markdown -## [1.2.0] - 2025-01-20 - -### ? Aggiunte -- Notifiche email per aste vinte -- Configurazione SMTP in Settings -``` - -### Scenario 3: Breaking Change - -**Situazione:** API REST completamente ristrutturata - -```xml - -1.2.0 - - -2.0.0 -``` - -```markdown -## [2.0.0] - 2025-02-01 - -### ?? BREAKING CHANGES -- API REST ristrutturata (endpoints modificati) -- Migrazione richiesta per client esistenti - -### ?? Modifiche -- Endpoint `/api/auctions` ? `/api/v2/auctions` -- Response format JSON standardizzato -``` - ---- - -## ?? Automazione - -### GitHub Actions / Gitea Actions - -```yaml -# .gitea/workflows/version-check.yml -name: Version Check - -on: - push: - branches: [ main, docker ] - -jobs: - check-version: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Extract version - id: version - run: | - VERSION=$(grep -oP '\K[^<]+' AutoBidder.csproj | head -1) - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Check CHANGELOG - run: | - if ! grep -q "## \[${{ steps.version.outputs.version }}\]" CHANGELOG.md; then - echo "?? Versione ${{ steps.version.outputs.version }} non documentata in CHANGELOG.md" - exit 1 - fi - - - name: Create Git Tag - run: | - git tag v${{ steps.version.outputs.version }} - git push origin v${{ steps.version.outputs.version }} -``` - -### PowerShell Script Locale - -```powershell -# scripts/bump-version.ps1 -param( - [Parameter(Mandatory=$true)] - [ValidateSet('major','minor','patch')] - [string]$Type -) - -# Leggi versione corrente -$csproj = "AutoBidder.csproj" -$content = Get-Content $csproj -Raw -$version = [regex]::Match($content, '(.*?)').Groups[1].Value - -# Parse semantic version -$parts = $version -split '\.' -$major = [int]$parts[0] -$minor = [int]$parts[1] -$patch = [int]$parts[2] - -# Incrementa -switch ($Type) { - 'major' { $major++; $minor=0; $patch=0 } - 'minor' { $minor++; $patch=0 } - 'patch' { $patch++ } -} - -$newVersion = "$major.$minor.$patch" - -# Aggiorna .csproj -$content = $content -replace '.*?', "$newVersion" -$content = $content -replace '.*?', "$newVersion.0" -$content = $content -replace '.*?', "$newVersion.0" -$content = $content -replace '.*?', "$newVersion" -Set-Content $csproj $content - -# Aggiorna Dockerfile -$dockerfile = "Dockerfile" -$dockerContent = Get-Content $dockerfile -Raw -$dockerContent = $dockerContent -replace 'org.opencontainers.image.version=".*?"', "org.opencontainers.image.version=""$newVersion""" -Set-Content $dockerfile $dockerContent - -Write-Host "? Versione aggiornata: $version ? $newVersion" -Write-Host "?? Ricorda di aggiornare CHANGELOG.md!" -``` - -**Uso:** -```powershell -# Incrementa PATCH (bug fix) -.\scripts\bump-version.ps1 -Type patch - -# Incrementa MINOR (nuova feature) -.\scripts\bump-version.ps1 -Type minor - -# Incrementa MAJOR (breaking change) -.\scripts\bump-version.ps1 -Type major -``` - ---- - -## ?? Riferimenti - -- [Semantic Versioning 2.0.0](https://semver.org/) -- [Keep a Changelog](https://keepachangelog.com/) -- [Conventional Commits](https://www.conventionalcommits.org/) -- [GitVersion](https://gitversion.net/) (tool automatico) - ---- - -## ? Checklist Release - -Prima di ogni release: - -- [ ] Versione incrementata in `AutoBidder.csproj` -- [ ] Versione aggiornata in `Dockerfile` labels -- [ ] Modifiche documentate in `CHANGELOG.md` -- [ ] Build locale testata -- [ ] Container Docker testato localmente -- [ ] Pubblicazione su Gitea completata -- [ ] Tag Git creato (`v1.1.0`) -- [ ] Documentazione aggiornata (se necessario) - -**Dopo la release:** - -- [ ] Verifica immagine su Gitea -- [ ] Test pull e deploy -- [ ] Comunicazione team (se applicabile) -- [ ] Aggiornamento deployment production - ---- - -**?? Versione corrente:** `1.1.0` - Docker/Gitea Publishing Workflow diff --git a/Mimante/VERSIONING_IMPLEMENTATO.md b/Mimante/VERSIONING_IMPLEMENTATO.md deleted file mode 100644 index 595d19c..0000000 --- a/Mimante/VERSIONING_IMPLEMENTATO.md +++ /dev/null @@ -1,340 +0,0 @@ -# ?? SISTEMA VERSIONAMENTO IMPLEMENTATO - -## ? Versione Corrente: `1.1.0` - -**Data:** 2025-01-18 -**Tipo:** MINOR (nuove feature + bug fix) -**Modifiche:** Docker/Gitea Publishing Workflow + HTTPS Fix - ---- - -## ?? File Creati/Aggiornati - -### Nuovi File - -1. **`CHANGELOG.md`** - - Storico completo modifiche - - Formato [Keep a Changelog](https://keepachangelog.com/) - - Documentazione v1.1.0 completa - -2. **`VERSIONING.md`** - - Guida sistema versionamento - - Workflow di rilascio - - Esempi pratici - - Automazione - -3. **`bump-version.ps1`** - - Script PowerShell automatico - - Incrementa MAJOR/MINOR/PATCH - - Aggiorna tutti i file coinvolti - - Genera template CHANGELOG - -### File Aggiornati - -1. **`AutoBidder.csproj`** - ```xml - - 1.1.0 - 1.1.0.0 - 1.1.0.0 - 1.1.0 - ``` - -2. **`Dockerfile`** - ```dockerfile - LABEL org.opencontainers.image.version="1.1.0" - ``` - ---- - -## ?? Come Usare il Sistema - -### Metodo 1: Script Automatico (CONSIGLIATO) - -```powershell -# Bug fix (1.1.0 ? 1.1.1) -.\bump-version.ps1 -Type patch - -# Nuova feature (1.1.0 ? 1.2.0) -.\bump-version.ps1 -Type minor - -# Breaking change (1.1.0 ? 2.0.0) -.\bump-version.ps1 -Type major -``` - -**Lo script fa automaticamente:** -1. ? Incrementa versione in `AutoBidder.csproj` -2. ? Aggiorna `Dockerfile` labels -3. ? Aggiunge template in `CHANGELOG.md` -4. ? Mostra prossimi passi - -### Metodo 2: Manuale - -1. **Modifica `AutoBidder.csproj`:** - ```xml - 1.2.0 - ``` - -2. **Modifica `Dockerfile`:** - ```dockerfile - LABEL org.opencontainers.image.version="1.2.0" - ``` - -3. **Aggiorna `CHANGELOG.md`:** - ```markdown - ## [1.2.0] - 2025-01-19 - - ### ? Aggiunte - - Nuova feature X - ``` - -4. **Pubblica:** - ```bash - dotnet publish /p:PublishProfile=GiteaRegistry - ``` - ---- - -## ?? Workflow Completo di Rilascio - -### Step 1: Incrementa Versione - -```powershell -.\bump-version.ps1 -Type minor -``` - -### Step 2: Compila CHANGELOG - -Apri `CHANGELOG.md` e completa il template: - -```markdown -## [1.2.0] - 2025-01-19 - -### ? Aggiunte -- Feature notifiche email per aste vinte -- Configurazione SMTP in Settings - -### ?? Modifiche -- Migliorato algoritmo calcolo statistiche - -### ?? Correzioni -- Fix bug crash su asta annullata -``` - -### Step 3: Commit Modifiche - -```bash -git add AutoBidder.csproj Dockerfile CHANGELOG.md -git commit -m "chore: bump version to v1.2.0 - -- Feature notifiche email -- Fix bug crash asta annullata" -``` - -### Step 4: Tag Git - -```bash -git tag v1.2.0 -git push origin docker --tags -``` - -### Step 5: Pubblica Docker su Gitea - -**Da Visual Studio:** -- Tasto destro ? Pubblica ? GiteaRegistry - -**Da CLI:** -```bash -dotnet publish /p:PublishProfile=GiteaRegistry -``` - -### Step 6: Verifica Pubblicazione - -```bash -# Controlla su Gitea -https://gitea.encke-hake.ts.net/Alby96/-/packages/container/autobidder - -# Verifica tag creati -docker pull gitea.encke-hake.ts.net/alby96/autobidder:latest -docker pull gitea.encke-hake.ts.net/alby96/autobidder:1.2.0 -``` - ---- - -## ?? Semantic Versioning - -| Versione | Tipo | Quando Usare | Esempio | -|----------|------|--------------|---------| -| **1.0.0 ? 2.0.0** | MAJOR | Breaking changes | API cambiata, porta diversa | -| **1.0.0 ? 1.1.0** | MINOR | Nuove feature | Notifiche email, esportazione dati | -| **1.0.0 ? 1.0.1** | PATCH | Bug fix | Fix crash, correzione calcoli | - -### Esempi Pratici - -**Bug Fix (PATCH):** -```powershell -.\bump-version.ps1 -Type patch -# 1.1.0 ? 1.1.1 -``` - -**Nuova Feature (MINOR):** -```powershell -.\bump-version.ps1 -Type minor -# 1.1.1 ? 1.2.0 -``` - -**Breaking Change (MAJOR):** -```powershell -.\bump-version.ps1 -Type major -# 1.2.0 ? 2.0.0 -``` - ---- - -## ?? Tag Docker Generati - -### Dopo Pubblicazione v1.1.0 - -```bash -# Tag su Gitea -gitea.encke-hake.ts.net/alby96/autobidder:latest ? v1.1.0 -gitea.encke-hake.ts.net/alby96/autobidder:1.1.0 ? immutabile -gitea.encke-hake.ts.net/alby96/autobidder:1.0.0 ? ancora disponibile -``` - -### Production Best Practice - -**? NON USARE `latest` in production:** -```yaml -# ERRATO -image: gitea.../autobidder:latest -``` - -**? USA versione specifica:** -```yaml -# CORRETTO -image: gitea.../autobidder:1.1.0 -``` - -**Motivo:** `latest` cambia ad ogni release, versione specifica è immutabile. - ---- - -## ?? Gestione Hotfix - -### Scenario: Bug critico in production - -**Production usa:** `v1.1.0` -**Development è a:** `v1.2.0-dev` - -**Workflow:** - -1. **Crea branch hotfix:** - ```bash - git checkout -b hotfix/1.1.1 v1.1.0 - ``` - -2. **Applica fix:** - ```bash - # Fix bug - .\bump-version.ps1 -Type patch # 1.1.0 ? 1.1.1 - ``` - -3. **Pubblica hotfix:** - ```bash - git commit -m "fix: critical bug in auction monitoring" - git tag v1.1.1 - git push origin hotfix/1.1.1 --tags - dotnet publish /p:PublishProfile=GiteaRegistry - ``` - -4. **Merge in main:** - ```bash - git checkout docker - git merge hotfix/1.1.1 - ``` - -5. **Aggiorna development:** - ```bash - # Se necessario, cherry-pick il fix in v1.2.0-dev - git cherry-pick - ``` - ---- - -## ?? Dashboard Versioni - -### Versioni Attive - -| Versione | Stato | Tag Docker | Ambiente | -|----------|-------|------------|----------| -| `1.1.0` | ? Latest | `latest`, `1.1.0` | Production | -| `1.0.0` | ?? Deprecated | `1.0.0` | Legacy | - -### Roadmap - -| Versione | Tipo | Piano | Data Target | -|----------|------|-------|-------------| -| `1.2.0` | MINOR | Notifiche email | Feb 2025 | -| `1.3.0` | MINOR | API REST | Mar 2025 | -| `2.0.0` | MAJOR | Refactor architettura | Q2 2025 | - ---- - -## ? Checklist Release - -Prima di ogni release: - -- [ ] **Versione incrementata** in `AutoBidder.csproj` -- [ ] **Versione aggiornata** in `Dockerfile` -- [ ] **CHANGELOG.md** compilato con modifiche -- [ ] **Build locale** testata -- [ ] **Container Docker** testato localmente -- [ ] **Pubblicazione Gitea** completata -- [ ] **Tag Git** creato (`v1.1.0`) -- [ ] **Documentazione** aggiornata (se necessario) -- [ ] **Migration guide** scritta (per breaking changes) -- [ ] **Communication** team/utenti (se applicabile) - -Dopo la release: - -- [ ] **Verifica immagine** su Gitea -- [ ] **Test pull** e deploy -- [ ] **Monitoraggio** errori prime 24h -- [ ] **Aggiornamento** deployment production - ---- - -## ?? Benefici del Sistema - -### Prima (senza versioning) -- ? Versioni non tracciate -- ? Modifiche non documentate -- ? Impossibile rollback a versione specifica -- ? Difficile capire cosa è cambiato - -### Dopo (con versioning) -- ? Ogni modifica tracciata con versione -- ? CHANGELOG completo e leggibile -- ? Rollback facile (`docker pull .../:1.0.0`) -- ? Deploy controllati e verificabili -- ? Automazione con script PowerShell -- ? Tag Docker immutabili per production - ---- - -## ?? Documenti di Riferimento - -| File | Scopo | -|------|-------| -| `CHANGELOG.md` | Storico modifiche per utenti | -| `VERSIONING.md` | Guida sistema per sviluppatori | -| `bump-version.ps1` | Automazione incremento versione | -| `AutoBidder.csproj` | Fonte unica della verità (versione) | -| `Dockerfile` | Metadata versione immagine | - ---- - -**?? Versione attuale: `1.1.0` - Docker/Gitea Publishing Workflow** - -**? Sistema di versionamento completamente implementato e operativo!** diff --git a/Mimante/wwwroot/css/animations.css b/Mimante/wwwroot/css/animations.css index 41f077e..963d70b 100644 --- a/Mimante/wwwroot/css/animations.css +++ b/Mimante/wwwroot/css/animations.css @@ -412,8 +412,9 @@ transition: all 0.2s ease; } +/* Rimosso effetto scale sulle righe - era fastidioso */ .table tbody tr:hover { - transform: scale(1.01); + /* transform: scale(1.01); - RIMOSSO */ z-index: 1; } @@ -431,8 +432,7 @@ transition: all 0.3s ease; } -.badge:hover { - transform: scale(1.1); +/* Rimosso effetto scale su badge hover */ } .badge-pulse { diff --git a/Mimante/wwwroot/css/app-wpf.css b/Mimante/wwwroot/css/app-wpf.css index b01dac9..118b181 100644 --- a/Mimante/wwwroot/css/app-wpf.css +++ b/Mimante/wwwroot/css/app-wpf.css @@ -1,4 +1,4 @@ -/* app-wpf.css - Modern Dark Theme */ +/* app-wpf.css - Modern Dark Theme */ :root { /* Modern Dark Palette */ @@ -558,6 +558,7 @@ main { overflow: auto; } + /* Splitter verticale tra griglia e log */ .splitter-vertical { grid-column: 2; @@ -566,22 +567,28 @@ main { cursor: col-resize; position: relative; transition: background 0.2s ease; + min-width: 6px; + width: 6px; } .splitter-vertical:hover { - background: var(--primary-color); + background: var(--primary); } -.splitter-vertical::after { - content: ''; +.splitter-vertical::before { + content: 'â‹®'; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); - width: 2px; - height: 40px; - background: var(--text-muted); - border-radius: 1px; + color: var(--text-muted); + font-size: 16px; + opacity: 0.5; +} + +.splitter-vertical:hover::before { + color: white; + opacity: 1; } /* Log globale - colonna destra */ @@ -598,7 +605,7 @@ main { /* Splitter orizzontale tra top e dettagli */ .splitter-horizontal { - height: 4px; + height: 6px; background: var(--border-color); cursor: row-resize; position: relative; @@ -607,19 +614,23 @@ main { } .splitter-horizontal:hover { - background: var(--primary-color); + background: var(--primary); } -.splitter-horizontal::after { - content: ''; +.splitter-horizontal::before { + content: '⋯'; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); - width: 40px; - height: 2px; - background: var(--text-muted); - border-radius: 1px; + color: var(--text-muted); + font-size: 16px; + opacity: 0.5; +} + +.splitter-horizontal:hover::before { + color: white; + opacity: 1; } /* Dettagli asta - sotto splitter orizzontale */ @@ -1513,8 +1524,8 @@ main { .table-fixed .col-prezzo { width: 90px; } .table-fixed .col-timer { width: 90px; } .table-fixed .col-ultimo { width: 120px; } -.table-fixed .col-click { width: 70px; text-align: center; } -.table-fixed .col-ping { width: 80px; } +.table-fixed .col-click { width: 90px; text-align: center; padding-right: 10px; } +.table-fixed .col-ping { width: 90px; padding-left: 10px; } .table-fixed .col-azioni { width: 150px; } .table-fixed td { diff --git a/Mimante/wwwroot/js/log-scroll.js b/Mimante/wwwroot/js/log-scroll.js index aea76ce..2a5a2d2 100644 --- a/Mimante/wwwroot/js/log-scroll.js +++ b/Mimante/wwwroot/js/log-scroll.js @@ -76,6 +76,7 @@ window.Blazor.addEventListener('enhancedload', initLogScroll); } + // Esporta funzione per forzare scroll window.forceLogScrollToBottom = function () { logBoxes.forEach(logBox => { @@ -83,4 +84,18 @@ scrollToBottom(logBox); }); }; + + // Funzione chiamabile da Blazor per scroll specifico elemento + window.scrollToBottom = function (elementId) { + const element = document.getElementById(elementId); + if (element) { + // Controlla se siamo già in fondo o quasi (entro 100px) + const isNearBottom = element.scrollHeight - element.scrollTop - element.clientHeight < 100; + + // Auto-scroll solo se siamo già in fondo (non interrompe lettura manuale) + if (isNearBottom || !userScrolling.get(element)) { + element.scrollTop = element.scrollHeight; + } + } + }; })();