Refactoring: Docker, CI/CD, tema WPF, DB avanzato, UX
- Aggiunto sistema completo di build/deploy Docker, Makefile, compose, .env, workflow CI/CD (Gitea, GitHub Actions) - Nuovo servizio DatabaseService con migrations, healthcheck, backup, ottimizzazione, info - Endpoint /health per healthcheck container - Impostazioni avanzate di avvio aste (ricorda stato, auto-start, default nuove aste) - Nuovo tema grafico WPF: palette, sidebar, layout griglia, log colorati, badge, cards, modali, responsività - Migliorato calcolo valore prodotto, logica convenienza, blocco puntate non convenienti, log dettagliati - Semplificate e migliorate pagine FreeBids, Settings, Statistics; rimossa Browser.razor - Aggiornato .gitignore, documentazione, struttura progetto - Base solida per future funzionalità avanzate e deploy professionale
This commit is contained in:
@@ -1,28 +1,87 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
Documentation/
|
||||
.github/
|
||||
**Dockerignore file**
|
||||
|
||||
# Build artifacts
|
||||
**/bin/
|
||||
**/obj/
|
||||
**/out/
|
||||
**/publish/
|
||||
|
||||
# User-specific files
|
||||
*.user
|
||||
*.suo
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# IDE files
|
||||
.vs/
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# NuGet packages
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
**/packages/*
|
||||
!**/packages/build/
|
||||
|
||||
# Test results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# Data and databases (exclude from image)
|
||||
**/data/*.db
|
||||
**/data/*.db-shm
|
||||
**/data/*.db-wal
|
||||
**/data/backups/
|
||||
**/data/logs/
|
||||
|
||||
# Git files
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
.github/
|
||||
|
||||
# CI/CD files
|
||||
.gitea/
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Docker files
|
||||
Dockerfile*
|
||||
docker-compose*
|
||||
.dockerignore
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.cache
|
||||
*.bak
|
||||
*.log
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
71
Mimante/.env.example
Normal file
71
Mimante/.env.example
Normal file
@@ -0,0 +1,71 @@
|
||||
# AutoBidder Environment Variables
|
||||
# Copia questo file in .env e configura i valori
|
||||
|
||||
# === ASP.NET Core Configuration ===
|
||||
ASPNETCORE_ENVIRONMENT=Production
|
||||
ASPNETCORE_URLS=http://+:5000;https://+:5001
|
||||
|
||||
# === HTTPS Certificate ===
|
||||
# Password per il certificato PFX
|
||||
CERT_PASSWORD=AutoBidder2024
|
||||
|
||||
# === Gitea Container Registry ===
|
||||
# URL del registry (senza https://)
|
||||
GITEA_REGISTRY=192.168.30.23/Alby96
|
||||
|
||||
# Username Gitea
|
||||
GITEA_USERNAME=Alby96
|
||||
|
||||
# Access Token Gitea (genera su: https://192.168.30.23/user/settings/applications)
|
||||
# Scope richiesti: write:package, read:package
|
||||
GITEA_PASSWORD=ghp_your_token_here
|
||||
|
||||
# === Deployment Configuration ===
|
||||
# IP o hostname del server di deploy
|
||||
DEPLOY_HOST=192.168.30.23
|
||||
|
||||
# User SSH per deploy
|
||||
DEPLOY_USER=deploy
|
||||
|
||||
# Path alla chiave privata SSH (per CI/CD)
|
||||
# DEPLOY_SSH_KEY_PATH=/path/to/ssh/key
|
||||
|
||||
# === Database Configuration ===
|
||||
# Path database (default: /app/data/autobidder.db in container)
|
||||
# DATABASE_PATH=/app/data/autobidder.db
|
||||
|
||||
# === Logging ===
|
||||
# Livello log: Trace, Debug, Information, Warning, Error, Critical
|
||||
# LOG_LEVEL=Information
|
||||
|
||||
# === Application Settings ===
|
||||
# Numero massimo connessioni concorrenti HTTP
|
||||
# MAX_HTTP_CONNECTIONS=10
|
||||
|
||||
# Timeout richieste HTTP (secondi)
|
||||
# HTTP_TIMEOUT=30
|
||||
|
||||
# === Backup Configuration ===
|
||||
# Directory backup (default: /app/data/backups)
|
||||
# BACKUP_DIR=/app/data/backups
|
||||
|
||||
# Numero giorni backup da mantenere
|
||||
# BACKUP_RETENTION_DAYS=30
|
||||
|
||||
# === Security ===
|
||||
# Chiave segreta per DataProtection (genera random se non specificato)
|
||||
# DATA_PROTECTION_KEY=your-random-key-here
|
||||
|
||||
# === Monitoring ===
|
||||
# Abilita metriche Prometheus (true/false)
|
||||
# ENABLE_METRICS=false
|
||||
|
||||
# Porta metriche (se ENABLE_METRICS=true)
|
||||
# METRICS_PORT=9090
|
||||
|
||||
# === Advanced ===
|
||||
# Numero thread worker per polling aste
|
||||
# AUCTION_WORKER_THREADS=4
|
||||
|
||||
# Intervallo pulizia cache (minuti)
|
||||
# CACHE_CLEANUP_INTERVAL=60
|
||||
89
Mimante/.gitea/workflows/deploy.yml
Normal file
89
Mimante/.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Build and Deploy AutoBidder
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- docker
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-restore --verbosity normal
|
||||
continue-on-error: true
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish --configuration Release --no-build --output ./publish
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Gitea Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ secrets.GITEA_REGISTRY }}
|
||||
username: ${{ secrets.GITEA_USERNAME }}
|
||||
password: ${{ secrets.GITEA_PASSWORD }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ secrets.GITEA_REGISTRY }}/autobidder
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ secrets.GITEA_REGISTRY }}/autobidder:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.GITEA_REGISTRY }}/autobidder:buildcache,mode=max
|
||||
|
||||
deploy:
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/docker'
|
||||
|
||||
steps:
|
||||
- name: Deploy to server
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.DEPLOY_HOST }}
|
||||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
script: |
|
||||
cd /opt/autobidder
|
||||
docker-compose pull
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
docker-compose logs -f --tail=50
|
||||
97
Mimante/.github/workflows/ci-cd.yml
vendored
Normal file
97
Mimante/.github/workflows/ci-cd.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: AutoBidder CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, docker ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
env:
|
||||
REGISTRY: 192.168.30.23/Alby96
|
||||
IMAGE_NAME: autobidder
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Test
|
||||
run: dotnet test --no-restore --verbosity normal
|
||||
continue-on-error: true
|
||||
|
||||
- name: Publish
|
||||
run: dotnet publish --configuration Release --no-build --output ./publish
|
||||
|
||||
docker-build-push:
|
||||
needs: build-and-test
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Gitea Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ secrets.GITEA_REGISTRY }}
|
||||
username: ${{ secrets.GITEA_USERNAME }}
|
||||
password: ${{ secrets.GITEA_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
|
||||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
|
||||
|
||||
deploy:
|
||||
needs: docker-build-push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/docker' || github.ref == 'refs/heads/main'
|
||||
|
||||
steps:
|
||||
- name: Deploy to server
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.DEPLOY_HOST }}
|
||||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
script: |
|
||||
cd /opt/autobidder
|
||||
source .env
|
||||
echo "$GITEA_PASSWORD" | docker login $GITEA_REGISTRY -u $GITEA_USERNAME --password-stdin
|
||||
docker-compose pull
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
sleep 10
|
||||
docker-compose ps
|
||||
docker-compose logs --tail=30
|
||||
456
Mimante/.gitignore
vendored
Normal file
456
Mimante/.gitignore
vendored
Normal file
@@ -0,0 +1,456 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
.idea/
|
||||
|
||||
# ============================================
|
||||
# AutoBidder Specific
|
||||
# ============================================
|
||||
|
||||
# Database files (local development)
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
data/*.db
|
||||
data/*.db-*
|
||||
|
||||
# Backups (keep structure, ignore files)
|
||||
data/backups/*.db
|
||||
data/backups/*.json
|
||||
|
||||
# Logs
|
||||
logs/*.log
|
||||
logs/*.txt
|
||||
*.log
|
||||
|
||||
# Environment files with secrets
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Certificates and keys
|
||||
*.pfx
|
||||
*.key
|
||||
*.crt
|
||||
*.pem
|
||||
cert/*
|
||||
!cert/.gitkeep
|
||||
|
||||
# Docker volumes data
|
||||
test-data/
|
||||
|
||||
# Published artifacts
|
||||
publish/
|
||||
PublishProfiles/
|
||||
|
||||
# Temp directories
|
||||
temp/
|
||||
tmp/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Keep important empty directories
|
||||
!data/.gitkeep
|
||||
!data/backups/.gitkeep
|
||||
!logs/.gitkeep
|
||||
!cert/.gitkeep
|
||||
@@ -59,4 +59,13 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\js\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include=".gitea\workflows\deploy.yml" />
|
||||
<None Include=".github\workflows\ci-cd.yml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
1
Mimante/Data/.gitkeep
Normal file
1
Mimante/Data/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file ensures the data directory is tracked by git
|
||||
@@ -1,21 +1,69 @@
|
||||
# Usa l'immagine base ASP.NET Runtime
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
|
||||
# Usa l'immagine SDK per build
|
||||
# Stage 1: Build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copia solo i file di progetto per cache layer restore
|
||||
COPY ["AutoBidder.csproj", "./"]
|
||||
RUN dotnet restore "AutoBidder.csproj"
|
||||
|
||||
# Copia tutto il codice sorgente
|
||||
COPY . .
|
||||
RUN dotnet build "AutoBidder.csproj" -c Release -o /app/build
|
||||
|
||||
# Build con ottimizzazioni
|
||||
RUN dotnet build "AutoBidder.csproj" \
|
||||
-c Release \
|
||||
-o /app/build \
|
||||
--no-restore
|
||||
|
||||
# Stage 2: Publish
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "AutoBidder.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
RUN dotnet publish "AutoBidder.csproj" \
|
||||
-c Release \
|
||||
-o /app/publish \
|
||||
--no-restore \
|
||||
--no-build \
|
||||
/p:UseAppHost=false \
|
||||
/p:PublishTrimmed=false \
|
||||
/p:PublishSingleFile=false
|
||||
|
||||
# Immagine finale
|
||||
FROM base AS final
|
||||
# Stage 3: Runtime
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Installa curl per healthcheck e tools utili
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
sqlite3 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Crea directory per dati e certificati
|
||||
RUN mkdir -p /app/data /app/data/backups /app/cert /app/logs && \
|
||||
chmod 755 /app/data /app/cert /app/logs
|
||||
|
||||
# Copia artifacts da publish stage
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
# Esponi porte
|
||||
EXPOSE 5000
|
||||
EXPOSE 5001
|
||||
|
||||
# Healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/health || exit 1
|
||||
|
||||
# User non-root per sicurezza
|
||||
RUN useradd -m -u 1000 appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Labels per metadata
|
||||
LABEL org.opencontainers.image.title="AutoBidder" \
|
||||
org.opencontainers.image.description="Sistema automatizzato di gestione aste Blazor" \
|
||||
org.opencontainers.image.version="1.0.0" \
|
||||
org.opencontainers.image.vendor="Alby96" \
|
||||
org.opencontainers.image.source="https://192.168.30.23/Alby96/Mimante"
|
||||
|
||||
# Entrypoint
|
||||
ENTRYPOINT ["dotnet", "AutoBidder.dll"]
|
||||
|
||||
186
Mimante/Makefile
Normal file
186
Mimante/Makefile
Normal file
@@ -0,0 +1,186 @@
|
||||
.PHONY: help build run stop logs clean deploy backup test
|
||||
|
||||
# Variables
|
||||
IMAGE_NAME := autobidder
|
||||
CONTAINER_NAME := autobidder
|
||||
REGISTRY := 192.168.30.23/Alby96
|
||||
TAG := latest
|
||||
COMPOSE_FILE := docker-compose.yml
|
||||
|
||||
# Colors
|
||||
GREEN := \033[0;32m
|
||||
YELLOW := \033[1;33m
|
||||
NC := \033[0m
|
||||
|
||||
help: ## Mostra questo messaggio di aiuto
|
||||
@echo "$(GREEN)AutoBidder - Makefile Commands$(NC)"
|
||||
@echo "================================"
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(YELLOW)%-15s$(NC) %s\n", $$1, $$2}'
|
||||
|
||||
build: ## Build Docker image
|
||||
@echo "$(GREEN)Building Docker image...$(NC)"
|
||||
docker build -t $(IMAGE_NAME):$(TAG) .
|
||||
|
||||
build-no-cache: ## Build Docker image senza cache
|
||||
@echo "$(GREEN)Building Docker image (no cache)...$(NC)"
|
||||
docker build --no-cache -t $(IMAGE_NAME):$(TAG) .
|
||||
|
||||
run: ## Avvia container con docker-compose
|
||||
@echo "$(GREEN)Starting containers...$(NC)"
|
||||
docker-compose -f $(COMPOSE_FILE) up -d
|
||||
|
||||
stop: ## Ferma container
|
||||
@echo "$(GREEN)Stopping containers...$(NC)"
|
||||
docker-compose -f $(COMPOSE_FILE) down
|
||||
|
||||
restart: stop run ## Restart container
|
||||
|
||||
logs: ## Mostra logs real-time
|
||||
docker-compose -f $(COMPOSE_FILE) logs -f
|
||||
|
||||
logs-tail: ## Mostra ultimi 100 logs
|
||||
docker-compose -f $(COMPOSE_FILE) logs --tail=100
|
||||
|
||||
ps: ## Mostra status container
|
||||
docker-compose -f $(COMPOSE_FILE) ps
|
||||
|
||||
shell: ## Apri shell nel container
|
||||
docker-compose -f $(COMPOSE_FILE) exec autobidder /bin/bash
|
||||
|
||||
clean: ## Pulisci container, immagini e volumi
|
||||
@echo "$(GREEN)Cleaning up...$(NC)"
|
||||
docker-compose -f $(COMPOSE_FILE) down -v
|
||||
docker rmi $(IMAGE_NAME):$(TAG) 2>/dev/null || true
|
||||
|
||||
clean-all: ## Pulisci tutto inclusi dati (PERICOLOSO!)
|
||||
@echo "$(YELLOW)Warning: This will delete all data!$(NC)"
|
||||
@read -p "Are you sure? [y/N] " -n 1 -r; \
|
||||
echo; \
|
||||
if [[ $$REPLY =~ ^[Yy]$$ ]]; then \
|
||||
chmod +x scripts/clean.sh; \
|
||||
./scripts/clean.sh; \
|
||||
fi
|
||||
|
||||
clean-build: ## Pulisci solo build artifacts
|
||||
@echo "$(GREEN)Cleaning build artifacts...$(NC)"
|
||||
dotnet clean
|
||||
rm -rf bin/ obj/ publish/
|
||||
|
||||
tag: ## Tag immagine per registry
|
||||
@echo "$(GREEN)Tagging image for registry...$(NC)"
|
||||
docker tag $(IMAGE_NAME):$(TAG) $(REGISTRY)/$(IMAGE_NAME):$(TAG)
|
||||
|
||||
push: tag ## Push immagine su registry
|
||||
@echo "$(GREEN)Pushing image to registry...$(NC)"
|
||||
docker push $(REGISTRY)/$(IMAGE_NAME):$(TAG)
|
||||
|
||||
pull: ## Pull immagine da registry
|
||||
@echo "$(GREEN)Pulling image from registry...$(NC)"
|
||||
docker pull $(REGISTRY)/$(IMAGE_NAME):$(TAG)
|
||||
|
||||
login: ## Login al registry Gitea
|
||||
@echo "$(GREEN)Logging in to registry...$(NC)"
|
||||
@read -p "Username: " username; \
|
||||
docker login $(REGISTRY) -u $$username
|
||||
|
||||
deploy: pull run ## Deploy (pull + run)
|
||||
@echo "$(GREEN)Deployment completed!$(NC)"
|
||||
|
||||
backup: ## Backup database
|
||||
@echo "$(GREEN)Creating backup...$(NC)"
|
||||
@mkdir -p ./data/backups
|
||||
@if [ -f ./data/autobidder.db ]; then \
|
||||
cp ./data/autobidder.db ./data/backups/autobidder_$$(date +%Y%m%d_%H%M%S).db; \
|
||||
echo "$(GREEN)Backup created$(NC)"; \
|
||||
else \
|
||||
echo "$(YELLOW)No database found$(NC)"; \
|
||||
fi
|
||||
|
||||
restore: ## Restore database da backup
|
||||
@echo "$(GREEN)Available backups:$(NC)"
|
||||
@ls -1 ./data/backups/*.db 2>/dev/null || echo "No backups found"
|
||||
@read -p "Enter backup filename: " backup; \
|
||||
if [ -f ./data/backups/$$backup ]; then \
|
||||
cp ./data/backups/$$backup ./data/autobidder.db; \
|
||||
echo "$(GREEN)Database restored$(NC)"; \
|
||||
docker-compose restart; \
|
||||
else \
|
||||
echo "$(YELLOW)Backup not found$(NC)"; \
|
||||
fi
|
||||
|
||||
test: ## Test build locale
|
||||
@echo "$(GREEN)Testing build...$(NC)"
|
||||
dotnet build -c Release
|
||||
dotnet test --no-build
|
||||
|
||||
publish: ## Publish artifacts locali
|
||||
@echo "$(GREEN)Publishing artifacts...$(NC)"
|
||||
dotnet publish -c Release -o ./publish
|
||||
|
||||
dev: ## Avvia in modalità development
|
||||
@echo "$(GREEN)Starting in development mode...$(NC)"
|
||||
dotnet run
|
||||
|
||||
dev-docker: ## Avvia con Docker Compose dev
|
||||
@echo "$(GREEN)Starting with Docker Compose (dev)...$(NC)"
|
||||
docker-compose -f docker-compose.dev.yml up
|
||||
|
||||
dev-docker-debug: ## Avvia con Docker Compose + SQLite browser
|
||||
@echo "$(GREEN)Starting with Docker Compose + SQLite browser...${NC}"
|
||||
docker-compose -f docker-compose.dev.yml --profile debug up
|
||||
|
||||
watch: ## Avvia con hot-reload
|
||||
@echo "$(GREEN)Starting with hot-reload...$(NC)"
|
||||
dotnet watch run
|
||||
|
||||
test-docker: ## Test Docker build locale
|
||||
@echo "$(GREEN)Testing Docker build...$(NC)"
|
||||
@chmod +x scripts/test-docker.sh
|
||||
@./scripts/test-docker.sh
|
||||
|
||||
health: ## Verifica health container
|
||||
@echo "$(GREEN)Checking container health...$(NC)"
|
||||
@docker inspect --format='{{.State.Health.Status}}' $(CONTAINER_NAME) 2>/dev/null || echo "Container not running"
|
||||
|
||||
stats: ## Mostra statistiche container
|
||||
docker stats $(CONTAINER_NAME) --no-stream
|
||||
|
||||
db-info: ## Mostra info database
|
||||
@echo "$(GREEN)Database information:$(NC)"
|
||||
@if [ -f ./data/autobidder.db ]; then \
|
||||
ls -lh ./data/autobidder.db; \
|
||||
echo "Backups:"; \
|
||||
ls -lh ./data/backups/*.db 2>/dev/null | tail -5 || echo "No backups"; \
|
||||
else \
|
||||
echo "$(YELLOW)No database found$(NC)"; \
|
||||
fi
|
||||
|
||||
optimize: ## Ottimizza database (VACUUM)
|
||||
@echo "$(GREEN)Optimizing database...$(NC)"
|
||||
docker-compose exec autobidder sqlite3 /app/data/autobidder.db "VACUUM;"
|
||||
@echo "$(GREEN)Database optimized$(NC)"
|
||||
|
||||
update: ## Update immagine e restart
|
||||
@echo "$(GREEN)Updating AutoBidder...$(NC)"
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
@echo "$(GREEN)Update completed$(NC)"
|
||||
|
||||
version: ## Mostra versione
|
||||
@echo "$(GREEN)AutoBidder Version:$(NC)"
|
||||
@cat VERSION 2>/dev/null || echo "VERSION file not found"
|
||||
@docker-compose exec autobidder dotnet AutoBidder.dll --version 2>/dev/null || echo "Container not running"
|
||||
|
||||
release: ## Crea nuova release (interattivo)
|
||||
@echo "$(GREEN)Creating new release...$(NC)"
|
||||
@chmod +x scripts/release.sh
|
||||
@./scripts/release.sh
|
||||
|
||||
setup: ## Setup ambiente sviluppo
|
||||
@echo "$(GREEN)Setting up development environment...$(NC)"
|
||||
dotnet restore
|
||||
@mkdir -p ./data ./data/backups ./cert ./logs
|
||||
@echo "$(GREEN)Setup completed$(NC)"
|
||||
|
||||
ci: build test ## Esegui CI pipeline locale
|
||||
@echo "$(GREEN)CI pipeline completed$(NC)"
|
||||
@@ -115,6 +115,12 @@ namespace AutoBidder.Models
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ProductValue? CalculatedValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ultimo stato ricevuto dal monitor per questa asta
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public AuctionState? LastState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Aggiunge una voce al log dell'asta con limite automatico di righe
|
||||
|
||||
@@ -1,292 +0,0 @@
|
||||
@page "/browser"
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<PageTitle>Browser - AutoBidder</PageTitle>
|
||||
|
||||
<div class="browser-container animate-fade-in p-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4 animate-fade-in-down">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-globe text-primary me-3" style="font-size: 2.5rem;"></i>
|
||||
<h2 class="mb-0 fw-bold">Browser Integrato</h2>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-secondary hover-lift" @onclick="NavigateBack" disabled="@(!canGoBack)">
|
||||
<i class="bi bi-arrow-left"></i> Indietro
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary hover-lift" @onclick="NavigateForward" disabled="@(!canGoForward)">
|
||||
<i class="bi bi-arrow-right"></i> Avanti
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary hover-lift" @onclick="RefreshPage">
|
||||
<i class="bi bi-arrow-clockwise"></i> Ricarica
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="browser-toolbar mb-3 animate-fade-in-up delay-100">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" @bind="currentUrl" @bind:event="oninput"
|
||||
placeholder="https://it.bidoo.com" readonly />
|
||||
<button class="btn btn-success hover-lift" @onclick="NavigateToBidoo">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Apri Bidoo
|
||||
</button>
|
||||
<button class="btn btn-info hover-lift" @onclick="ExtractCookie">
|
||||
<i class="bi bi-cookie"></i> Estrai Cookie
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="alert alert-warning border-0 shadow-sm animate-shake mb-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-exclamation-triangle-fill me-3"></i>
|
||||
<div>
|
||||
<strong>Limitazione Browser:</strong> @errorMessage
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(extractedCookie))
|
||||
{
|
||||
<div class="alert alert-success border-0 shadow-sm animate-scale-in mb-3">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center flex-grow-1">
|
||||
<i class="bi bi-check-circle-fill me-3" style="font-size: 1.5rem;"></i>
|
||||
<div class="flex-grow-1">
|
||||
<strong>Cookie Estratto:</strong>
|
||||
<div class="mt-2">
|
||||
<textarea class="form-control" rows="3" readonly>@extractedCookie</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<button class="btn btn-primary hover-lift" @onclick="CopyCookie">
|
||||
<i class="bi bi-clipboard"></i> Copia
|
||||
</button>
|
||||
<a href="/settings" class="btn btn-success hover-lift ms-2">
|
||||
<i class="bi bi-gear"></i> Vai a Impostazioni
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="browser-frame-container animate-fade-in-up delay-200">
|
||||
<iframe id="bidooFrame"
|
||||
src="@iframeUrl"
|
||||
class="browser-frame"
|
||||
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
|
||||
@ref="iframeElement">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info border-0 shadow-sm mt-3 animate-fade-in-up delay-300">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-info-circle-fill me-3 mt-1" style="font-size: 1.5rem;"></i>
|
||||
<div>
|
||||
<h6 class="fw-bold mb-2">?? Come Usare il Browser Integrato</h6>
|
||||
<ol class="mb-0">
|
||||
<li>Clicca su <strong>"Apri Bidoo"</strong> per caricare il sito</li>
|
||||
<li>Effettua il login con le tue credenziali Bidoo</li>
|
||||
<li>Clicca su <strong>"Estrai Cookie"</strong> per recuperare il cookie di sessione</li>
|
||||
<li>Il cookie verrà copiato automaticamente negli appunti</li>
|
||||
<li>Vai alla pagina <strong>Impostazioni</strong> e incollalo nella sezione "Sessione Bidoo"</li>
|
||||
</ol>
|
||||
<div class="alert alert-warning border-0 mt-2 mb-0">
|
||||
<small>
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<strong>Nota:</strong> A causa delle limitazioni CORS, il cookie potrebbe non essere accessibile automaticamente.
|
||||
In tal caso, usa gli strumenti sviluppatore del browser (F12) per estrarlo manualmente.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-secondary mt-4 animate-fade-in-up delay-400">
|
||||
<div class="card-header bg-secondary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-tools"></i> Metodo Alternativo (Consigliato)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Se l'iframe non funziona correttamente, usa questo metodo:</p>
|
||||
<div class="steps-list">
|
||||
<div class="step-item hover-lift">
|
||||
<div class="step-number">
|
||||
<i class="bi bi-1-circle-fill"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<h6 class="fw-bold">Apri Bidoo in una Nuova Scheda</h6>
|
||||
<a href="https://it.bidoo.com" target="_blank" class="btn btn-primary hover-lift mt-2">
|
||||
<i class="bi bi-box-arrow-up-right"></i> https://it.bidoo.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-item hover-lift">
|
||||
<div class="step-number">
|
||||
<i class="bi bi-2-circle-fill"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<h6 class="fw-bold">Estrai il Cookie Manualmente</h6>
|
||||
<p class="mb-0">Usa F12 ? Application/Storage ? Cookies ? bidoo.com ? Copia <code>__stattrb</code></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-item hover-lift">
|
||||
<div class="step-number">
|
||||
<i class="bi bi-3-circle-fill"></i>
|
||||
</div>
|
||||
<div class="step-content">
|
||||
<h6 class="fw-bold">Incolla nelle Impostazioni</h6>
|
||||
<a href="/settings" class="btn btn-success hover-lift mt-2">
|
||||
<i class="bi bi-gear-fill"></i> Vai alle Impostazioni
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.browser-container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.browser-frame-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-secondary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.browser-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.browser-toolbar {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private ElementReference iframeElement;
|
||||
private string currentUrl = "about:blank";
|
||||
private string iframeUrl = "about:blank";
|
||||
private bool canGoBack = false;
|
||||
private bool canGoForward = false;
|
||||
private string? errorMessage = null;
|
||||
private string? extractedCookie = null;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Inizializza listener iframe
|
||||
await JSRuntime.InvokeVoidAsync("initializeBrowserFrame");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateToBidoo()
|
||||
{
|
||||
try
|
||||
{
|
||||
iframeUrl = "https://it.bidoo.com";
|
||||
currentUrl = iframeUrl;
|
||||
errorMessage = null;
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Errore durante la navigazione: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateBack()
|
||||
{
|
||||
try
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("navigateBack");
|
||||
canGoBack = await JSRuntime.InvokeAsync<bool>("canGoBack");
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorMessage = "Navigazione indietro non disponibile";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NavigateForward()
|
||||
{
|
||||
try
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("navigateForward");
|
||||
canGoForward = await JSRuntime.InvokeAsync<bool>("canGoForward");
|
||||
}
|
||||
catch
|
||||
{
|
||||
errorMessage = "Navigazione avanti non disponibile";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshPage()
|
||||
{
|
||||
try
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("refreshFrame");
|
||||
errorMessage = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Errore durante il refresh: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractCookie()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Tenta di estrarre il cookie tramite JavaScript
|
||||
extractedCookie = await JSRuntime.InvokeAsync<string>("extractCookie", "bidoo.com");
|
||||
|
||||
if (string.IsNullOrEmpty(extractedCookie))
|
||||
{
|
||||
errorMessage = "Impossibile estrarre il cookie automaticamente. Le limitazioni CORS bloccano l'accesso. Usa il metodo manuale F12.";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copia automaticamente negli appunti
|
||||
await CopyCookie();
|
||||
errorMessage = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Errore estrazione cookie: {ex.Message}. Usa il metodo manuale con F12.";
|
||||
extractedCookie = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyCookie()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(extractedCookie))
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", extractedCookie);
|
||||
await JSRuntime.InvokeVoidAsync("alert", "? Cookie copiato negli appunti!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,406 +1,53 @@
|
||||
@page "/freebids"
|
||||
@inject AuctionMonitor AuctionMonitor
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<PageTitle>Puntate Gratuite - AutoBidder</PageTitle>
|
||||
|
||||
<div class="freebids-container animate-fade-in p-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4 animate-fade-in-down">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-gift-fill text-warning me-3" style="font-size: 2.5rem;"></i>
|
||||
<h2 class="mb-0 fw-bold">Puntate Gratuite</h2>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary hover-lift" @onclick="RefreshData">
|
||||
<i class="bi bi-arrow-clockwise"></i> Aggiorna
|
||||
</button>
|
||||
<button class="btn btn-success hover-lift" @onclick="ShowInfoModal">
|
||||
<i class="bi bi-info-circle"></i> Come Funziona
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-4 animate-fade-in-down">
|
||||
<i class="bi bi-gift-fill text-warning me-3" style="font-size: 2.5rem;"></i>
|
||||
<h2 class="mb-0 fw-bold">Puntate Gratuite</h2>
|
||||
</div>
|
||||
|
||||
<!-- Alert Feature Not Implemented -->
|
||||
<div class="alert alert-info border-0 shadow-sm animate-scale-in mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-clock-history me-3" style="font-size: 2.5rem;"></i>
|
||||
<!-- Feature Under Development Notice -->
|
||||
<div class="alert alert-info border-0 shadow-sm animate-scale-in">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-tools me-3 mt-1" style="font-size: 3rem;"></i>
|
||||
<div>
|
||||
<h5 class="mb-2"><strong>?? Funzionalità in Sviluppo</strong></h5>
|
||||
<p class="mb-2">
|
||||
Il sistema di raccolta automatica delle puntate gratuite è attualmente in fase di sviluppo.
|
||||
<h4 class="mb-3"><strong>Funzionalita in Sviluppo</strong></h4>
|
||||
<p class="mb-3">
|
||||
Il sistema di gestione delle puntate gratuite e attualmente in fase di sviluppo e sara disponibile in una prossima versione.
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
<strong>Prossimamente:</strong> Rilevamento automatico delle aste con puntate gratuite,
|
||||
partecipazione automatica e statistiche dettagliate.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="row g-3 mb-4 animate-fade-in-up delay-100">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-hover text-center">
|
||||
<div class="card-body">
|
||||
<i class="bi bi-gift text-primary" style="font-size: 2.5rem;"></i>
|
||||
<h4 class="mt-3 mb-1 fw-bold">@totalFreeBids</h4>
|
||||
<p class="text-muted mb-0">Puntate Gratuite Oggi</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-hover text-center">
|
||||
<div class="card-body">
|
||||
<i class="bi bi-check-circle text-success" style="font-size: 2.5rem;"></i>
|
||||
<h4 class="mt-3 mb-1 fw-bold">@usedFreeBids</h4>
|
||||
<p class="text-muted mb-0">Puntate Utilizzate</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-hover text-center">
|
||||
<div class="card-body">
|
||||
<i class="bi bi-clock text-warning" style="font-size: 2.5rem;"></i>
|
||||
<h4 class="mt-3 mb-1 fw-bold">@pendingFreeBids</h4>
|
||||
<p class="text-muted mb-0">In Attesa</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-hover text-center">
|
||||
<div class="card-body">
|
||||
<i class="bi bi-trophy text-info" style="font-size: 2.5rem;"></i>
|
||||
<h4 class="mt-3 mb-1 fw-bold">@totalWins</h4>
|
||||
<p class="text-muted mb-0">Aste Vinte</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Free Bids Table -->
|
||||
<div class="card shadow-hover animate-fade-in-up delay-200">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-list-ul"></i> Aste con Puntate Gratuite Disponibili</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-tag"></i> Prodotto</th>
|
||||
<th><i class="bi bi-gift"></i> Puntate Gratuite</th>
|
||||
<th><i class="bi bi-clock"></i> Scadenza</th>
|
||||
<th><i class="bi bi-currency-euro"></i> Prezzo Attuale</th>
|
||||
<th><i class="bi bi-speedometer2"></i> Stato</th>
|
||||
<th><i class="bi bi-gear"></i> Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (freeBidsAuctions.Count == 0)
|
||||
{
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-5">
|
||||
<div class="text-muted">
|
||||
<i class="bi bi-inbox" style="font-size: 3rem; display: block; margin-bottom: 1rem;"></i>
|
||||
<h5>Nessuna asta con puntate gratuite disponibile</h5>
|
||||
<p class="mb-0">Le aste appariranno automaticamente quando disponibili</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var auction in freeBidsAuctions)
|
||||
{
|
||||
<tr class="transition-all">
|
||||
<td class="fw-semibold">@auction.ProductName</td>
|
||||
<td>
|
||||
<span class="badge bg-warning text-dark">
|
||||
<i class="bi bi-gift-fill"></i> @auction.FreeBidsAvailable
|
||||
</span>
|
||||
</td>
|
||||
<td>@auction.ExpiryTime.ToString("dd/MM/yyyy HH:mm")</td>
|
||||
<td class="fw-bold text-success">€@auction.CurrentPrice.ToString("F2")</td>
|
||||
<td>
|
||||
<span class="badge @GetStatusBadgeClass(auction.Status)">
|
||||
@auction.Status
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-primary hover-scale" @onclick="() => UseFreeBids(auction)"
|
||||
disabled="@(auction.Status != "Disponibile")">
|
||||
<i class="bi bi-play-fill"></i> Usa
|
||||
</button>
|
||||
<button class="btn btn-info hover-scale" @onclick="() => ViewDetails(auction)">
|
||||
<i class="bi bi-eye-fill"></i> Dettagli
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Card -->
|
||||
<div class="card shadow-hover mt-4 animate-fade-in-up delay-300">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-sliders"></i> Configurazione Puntate Gratuite</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="autoCollect" @bind="autoCollectEnabled" />
|
||||
<label class="form-check-label" for="autoCollect">
|
||||
<i class="bi bi-magic"></i> Raccolta automatica puntate gratuite
|
||||
</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
Rileva e raccogli automaticamente le puntate gratuite disponibili
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="autoUse" @bind="autoUseEnabled" />
|
||||
<label class="form-check-label" for="autoUse">
|
||||
<i class="bi bi-lightning-charge"></i> Utilizzo automatico puntate gratuite
|
||||
</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
Usa automaticamente le puntate gratuite sulle aste selezionate
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-currency-euro"></i> Prezzo massimo per auto-uso:
|
||||
</label>
|
||||
<input type="number" step="0.01" class="form-control transition-colors" @bind="maxPriceAutoUse" />
|
||||
<small class="form-text text-muted">
|
||||
Usa puntate gratuite solo su aste sotto questo prezzo
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-hourglass-split"></i> Tempo minimo rimanente (minuti):
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="minTimeRemaining" />
|
||||
<small class="form-text text-muted">
|
||||
Tempo minimo prima della scadenza per usare puntate gratuite
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success hover-lift" @onclick="SaveConfiguration" disabled>
|
||||
<i class="bi bi-check-lg"></i> Salva Configurazione
|
||||
</button>
|
||||
<small class="text-muted ms-2">
|
||||
<i class="bi bi-info-circle"></i> Disponibile nella prossima versione
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- How It Works Guide -->
|
||||
<div class="card border-info mt-4 animate-fade-in-up delay-400">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-question-circle"></i> Come Funzioneranno le Puntate Gratuite</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-primary"><i class="bi bi-1-circle-fill"></i> Rilevamento Automatico</h6>
|
||||
<p>
|
||||
AutoBidder scansionerà continuamente Bidoo.com per identificare le aste che offrono puntate gratuite.
|
||||
</p>
|
||||
|
||||
<h6 class="fw-bold text-primary mt-3"><i class="bi bi-2-circle-fill"></i> Raccolta Puntate</h6>
|
||||
<p>
|
||||
Le puntate gratuite verranno raccolte automaticamente non appena disponibili, prima che scadano.
|
||||
</p>
|
||||
|
||||
<h6 class="fw-bold text-primary mt-3"><i class="bi bi-3-circle-fill"></i> Utilizzo Strategico</h6>
|
||||
<p>
|
||||
Le puntate verranno utilizzate secondo le tue preferenze configurate (prezzo max, tempo rimanente, ecc.).
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="fw-bold text-success"><i class="bi bi-check-circle-fill"></i> Vantaggi</h6>
|
||||
<ul>
|
||||
<li>? Zero costo per le puntate gratuite</li>
|
||||
<li>? Maggiori opportunità di vincita</li>
|
||||
<li>? Nessun rischio economico</li>
|
||||
<li>? Gestione completamente automatica</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="fw-bold text-warning mt-3"><i class="bi bi-exclamation-triangle-fill"></i> Note Importanti</h6>
|
||||
<ul>
|
||||
<li>?? Le puntate gratuite hanno scadenza limitata</li>
|
||||
<li>?? Disponibilità limitata per prodotto/utente</li>
|
||||
<li>?? Vincere comunque richiede pagamento prodotto</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-secondary border-0 mt-3 mb-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-lightbulb-fill me-3" style="font-size: 1.5rem;"></i>
|
||||
<div>
|
||||
<strong>Suggerimento:</strong> Combina le puntate gratuite con la strategia di bidding normale
|
||||
per massimizzare le tue possibilità di vincita minimizzando i costi.
|
||||
</div>
|
||||
<hr class="my-3" />
|
||||
<h5 class="mb-2"><i class="bi bi-lightbulb-fill text-warning"></i> Funzionalita Previste:</h5>
|
||||
<ul class="mb-3">
|
||||
<li><strong>Rilevamento Automatico:</strong> Scansione continua delle aste con puntate gratuite disponibili</li>
|
||||
<li><strong>Raccolta Automatica:</strong> Acquisizione automatica delle puntate gratuite prima della scadenza</li>
|
||||
<li><strong>Utilizzo Strategico:</strong> Uso intelligente delle puntate secondo criteri configurabili</li>
|
||||
<li><strong>Statistiche Dettagliate:</strong> Tracciamento completo di utilizzo, vincite e risparmi</li>
|
||||
<li><strong>Notifiche:</strong> Avvisi in tempo reale per nuove opportunita</li>
|
||||
</ul>
|
||||
<div class="alert alert-secondary border-0 mb-0">
|
||||
<i class="bi bi-info-circle-fill me-2"></i>
|
||||
<strong>Nota:</strong> Le puntate gratuite sono offerte speciali di Bidoo che permettono di partecipare
|
||||
ad alcune aste senza utilizzare i propri crediti. Questa funzionalita automatizzera completamente
|
||||
il processo di raccolta e utilizzo.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Info -->
|
||||
@if (showInfoModal)
|
||||
{
|
||||
<div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content animate-scale-in">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-info-circle-fill"></i> Informazioni Puntate Gratuite</h5>
|
||||
<button type="button" class="btn-close" @onclick="CloseInfoModal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h6 class="fw-bold text-primary">?? Cosa sono le Puntate Gratuite?</h6>
|
||||
<p>
|
||||
Le puntate gratuite sono offerte speciali di Bidoo che permettono di partecipare a determinate aste
|
||||
senza utilizzare i tuoi crediti di puntata.
|
||||
</p>
|
||||
|
||||
<h6 class="fw-bold text-primary mt-3">?? Come Funziona AutoBidder (Prossimamente)</h6>
|
||||
<ol>
|
||||
<li><strong>Rilevamento:</strong> Monitora continuamente Bidoo per nuove offerte</li>
|
||||
<li><strong>Raccolta:</strong> Raccoglie automaticamente le puntate gratuite disponibili</li>
|
||||
<li><strong>Utilizzo:</strong> Le usa strategicamente secondo le tue impostazioni</li>
|
||||
<li><strong>Report:</strong> Tiene traccia di utilizzo e risultati</li>
|
||||
</ol>
|
||||
|
||||
<h6 class="fw-bold text-success mt-3">? Vantaggi</h6>
|
||||
<ul>
|
||||
<li>Partecipa ad aste senza costi</li>
|
||||
<li>Testa prodotti prima di investire puntate normali</li>
|
||||
<li>Aumenta le probabilità di vincita generale</li>
|
||||
</ul>
|
||||
|
||||
<h6 class="fw-bold text-warning mt-3">?? Limitazioni</h6>
|
||||
<ul>
|
||||
<li>Scadenza temporale (di solito 24-48 ore)</li>
|
||||
<li>Disponibilità limitata per account</li>
|
||||
<li>Solo su prodotti selezionati da Bidoo</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary hover-lift" @onclick="CloseInfoModal">
|
||||
<i class="bi bi-check-lg"></i> Ho Capito
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<style>
|
||||
.freebids-container {
|
||||
max-width: 1600px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.alert ul {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.alert ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
// Stats
|
||||
private int totalFreeBids = 0;
|
||||
private int usedFreeBids = 0;
|
||||
private int pendingFreeBids = 0;
|
||||
private int totalWins = 0;
|
||||
|
||||
// Configuration
|
||||
private bool autoCollectEnabled = false;
|
||||
private bool autoUseEnabled = false;
|
||||
private decimal maxPriceAutoUse = 5.0m;
|
||||
private int minTimeRemaining = 10;
|
||||
|
||||
// Data
|
||||
private List<FreeBidAuction> freeBidsAuctions = new();
|
||||
private bool showInfoModal = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
LoadMockData();
|
||||
}
|
||||
|
||||
private void LoadMockData()
|
||||
{
|
||||
// Mock data for demonstration
|
||||
totalFreeBids = 0;
|
||||
usedFreeBids = 0;
|
||||
pendingFreeBids = 0;
|
||||
totalWins = 0;
|
||||
|
||||
// Empty list - funzionalità non ancora implementata
|
||||
freeBidsAuctions = new List<FreeBidAuction>();
|
||||
}
|
||||
|
||||
private async Task RefreshData()
|
||||
{
|
||||
LoadMockData();
|
||||
await JSRuntime.InvokeVoidAsync("alert", "?? Funzionalità in sviluppo. I dati verranno aggiornati nella prossima versione.");
|
||||
}
|
||||
|
||||
private void ShowInfoModal()
|
||||
{
|
||||
showInfoModal = true;
|
||||
}
|
||||
|
||||
private void CloseInfoModal()
|
||||
{
|
||||
showInfoModal = false;
|
||||
}
|
||||
|
||||
private async Task UseFreeBids(FreeBidAuction auction)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("alert", $"?? Funzionalità in sviluppo.\n\nSarà possibile utilizzare le puntate gratuite su: {auction.ProductName}");
|
||||
}
|
||||
|
||||
private async Task ViewDetails(FreeBidAuction auction)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("alert", $"?? Dettagli Asta\n\nProdotto: {auction.ProductName}\nPuntate Gratuite: {auction.FreeBidsAvailable}\nPrezzo: €{auction.CurrentPrice:F2}\nScadenza: {auction.ExpiryTime:dd/MM/yyyy HH:mm}");
|
||||
}
|
||||
|
||||
private async Task SaveConfiguration()
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("alert", "?? Configurazione sarà disponibile nella prossima versione.");
|
||||
}
|
||||
|
||||
private string GetStatusBadgeClass(string status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
"Disponibile" => "bg-success",
|
||||
"In Uso" => "bg-warning text-dark",
|
||||
"Scaduta" => "bg-danger",
|
||||
"Completata" => "bg-info",
|
||||
_ => "bg-secondary"
|
||||
};
|
||||
}
|
||||
|
||||
// Model
|
||||
private class FreeBidAuction
|
||||
{
|
||||
public string ProductName { get; set; } = "";
|
||||
public int FreeBidsAvailable { get; set; }
|
||||
public DateTime ExpiryTime { get; set; }
|
||||
public decimal CurrentPrice { get; set; }
|
||||
public string Status { get; set; } = "Disponibile";
|
||||
}
|
||||
}
|
||||
|
||||
35
Mimante/Pages/Health.razor
Normal file
35
Mimante/Pages/Health.razor
Normal file
@@ -0,0 +1,35 @@
|
||||
@page "/health"
|
||||
@inject DatabaseService DatabaseService
|
||||
@inject AuctionMonitor AuctionMonitor
|
||||
|
||||
@code {
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Verifica database
|
||||
var dbHealthy = await DatabaseService.CheckDatabaseHealthAsync();
|
||||
|
||||
// Verifica servizi
|
||||
var auctionsCount = AuctionMonitor.GetAuctions().Count;
|
||||
|
||||
if (dbHealthy)
|
||||
{
|
||||
// Ritorna 200 OK
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ritorna 500 Internal Server Error
|
||||
throw new Exception("Database health check failed");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Healthcheck fallito
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OK
|
||||
@@ -48,8 +48,10 @@
|
||||
<th><i class="bi bi-currency-euro"></i> Prezzo</th>
|
||||
<th><i class="bi bi-clock"></i> Timer</th>
|
||||
<th><i class="bi bi-person"></i> Ultimo</th>
|
||||
<th><i class="bi bi-arrow-repeat"></i> Reset</th>
|
||||
<th><i class="bi bi-hand-index"></i> Click</th>
|
||||
<th><i class="bi bi-calculator"></i> Totale</th>
|
||||
<th><i class="bi bi-piggy-bank"></i> Risparmio</th>
|
||||
<th><i class="bi bi-check-circle"></i> OK?</th>
|
||||
<th><i class="bi bi-gear"></i> Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -68,8 +70,10 @@
|
||||
<td class="@GetPriceClass(auction)">@GetPriceDisplay(auction)</td>
|
||||
<td>@GetTimerDisplay(auction)</td>
|
||||
<td>@GetLastBidder(auction)</td>
|
||||
<td><span class="badge bg-secondary">@auction.ResetCount</span></td>
|
||||
<td><span class="badge bg-info">@GetMyBidsCount(auction)</span></td>
|
||||
<td class="@GetPriceClass(auction)">@GetTotalCostDisplay(auction)</td>
|
||||
<td class="@GetSavingsClass(auction)">@GetSavingsDisplay(auction)</td>
|
||||
<td><span class="@GetIsWorthItClass(auction)">@GetIsWorthItIcon(auction)</span></td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" @onclick:stopPropagation="true">
|
||||
@if (auction.IsActive && !auction.IsPaused)
|
||||
@@ -103,23 +107,42 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- LOG GLOBALE - ALTO DESTRA -->
|
||||
<div class="global-log animate-fade-in-up delay-200">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0"><i class="bi bi-terminal"></i> Log Globale</h4>
|
||||
<button class="btn btn-sm btn-secondary" @onclick="ClearGlobalLog">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="log-box">
|
||||
@if (globalLog.Count == 0)
|
||||
{
|
||||
<div class="text-muted"><i class="bi bi-inbox"></i> Nessun log ancora...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var logEntry in globalLog.TakeLast(100))
|
||||
{
|
||||
<div class="@GetLogEntryClass(logEntry)">@logEntry</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DETTAGLI ASTA - BASSO DESTRA -->
|
||||
@if (selectedAuction != null)
|
||||
{
|
||||
<div class="auction-details animate-fade-in-right delay-200 shadow-hover">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h3 class="mb-0"><i class="bi bi-info-circle-fill text-primary"></i> @selectedAuction.Name</h3>
|
||||
<span class="badge @GetStatusBadgeClass(selectedAuction) badge-glow fs-6">
|
||||
@GetStatusIcon(selectedAuction) @GetStatusText(selectedAuction)
|
||||
</span>
|
||||
</div>
|
||||
<div class="auction-details animate-fade-in-right delay-300 shadow-hover">
|
||||
<h3><i class="bi bi-info-circle-fill"></i> @selectedAuction.Name</h3>
|
||||
<p><small class="text-muted"><i class="bi bi-hash"></i> ID: @selectedAuction.AuctionId</small></p>
|
||||
|
||||
<div class="auction-info animate-scale-in">
|
||||
<div class="auction-info">
|
||||
<div class="info-group">
|
||||
<label><i class="bi bi-link-45deg"></i> URL:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="@selectedAuction.OriginalUrl" readonly />
|
||||
<button class="btn btn-outline-secondary hover-glow" @onclick="() => CopyToClipboard(selectedAuction.OriginalUrl)" title="Copia">
|
||||
<button class="btn btn-outline-secondary" @onclick="() => CopyToClipboard(selectedAuction.OriginalUrl)" title="Copia">
|
||||
<i class="bi bi-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -127,160 +150,150 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-speedometer2"></i> Anticipo Puntata (ms):</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="selectedAuction.BidBeforeDeadlineMs" @bind:after="SaveAuctions" />
|
||||
<label><i class="bi bi-speedometer2"></i> Anticipo (ms):</label>
|
||||
<input type="number" class="form-control" @bind="selectedAuction.BidBeforeDeadlineMs" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-hand-index-thumb"></i> Max Click:</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="selectedAuction.MaxClicks" @bind:after="SaveAuctions" />
|
||||
<input type="number" class="form-control" @bind="selectedAuction.MaxClicks" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-currency-euro"></i> Min Prezzo (€):</label>
|
||||
<input type="number" step="0.01" class="form-control transition-colors" @bind="selectedAuction.MinPrice" @bind:after="SaveAuctions" />
|
||||
<label><i class="bi bi-currency-euro"></i> Min €:</label>
|
||||
<input type="number" step="0.01" class="form-control" @bind="selectedAuction.MinPrice" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-currency-euro"></i> Max Prezzo (€):</label>
|
||||
<input type="number" step="0.01" class="form-control transition-colors" @bind="selectedAuction.MaxPrice" @bind:after="SaveAuctions" />
|
||||
<label><i class="bi bi-currency-euro"></i> Max €:</label>
|
||||
<input type="number" step="0.01" class="form-control" @bind="selectedAuction.MaxPrice" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-3">
|
||||
<div class="row">
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-arrow-repeat"></i> Min Reset:</label>
|
||||
<input type="number" class="form-control" @bind="selectedAuction.MinResets" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
<div class="col-md-6 info-group">
|
||||
<label><i class="bi bi-arrow-repeat"></i> Max Reset:</label>
|
||||
<input type="number" class="form-control" @bind="selectedAuction.MaxResets" @bind:after="SaveAuctions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mt-2">
|
||||
<input type="checkbox" class="form-check-input" id="checkOpen" @bind="selectedAuction.CheckAuctionOpenBeforeBid" @bind:after="SaveAuctions" />
|
||||
<label class="form-check-label" for="checkOpen">
|
||||
<i class="bi bi-shield-check"></i> Verifica asta aperta prima di puntare
|
||||
Verifica asta aperta
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auction-log mt-4">
|
||||
<h4><i class="bi bi-journal-text"></i> Log Asta</h4>
|
||||
<div class="log-box">
|
||||
@if (GetAuctionLog(selectedAuction).Any())
|
||||
{
|
||||
@foreach (var logEntry in GetAuctionLog(selectedAuction))
|
||||
{
|
||||
<div class="log-entry-new">@logEntry</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-muted"><i class="bi bi-inbox"></i> Nessun log disponibile</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bidders-stats mt-4">
|
||||
<h4><i class="bi bi-people-fill"></i> Partecipanti (@selectedAuction.BidderStats.Count)</h4>
|
||||
@if (selectedAuction.BidderStats.Count == 0)
|
||||
|
||||
@* ?? NUOVO: Sezione Valore Prodotto *@
|
||||
@if (selectedAuction.CalculatedValue != null)
|
||||
{
|
||||
<p class="text-muted"><i class="bi bi-person-x"></i> Nessun partecipante ancora</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><i class="bi bi-person"></i> Utente</th>
|
||||
<th><i class="bi bi-hand-index"></i> Puntate</th>
|
||||
<th><i class="bi bi-clock-history"></i> Ultima</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var bidder in selectedAuction.BidderStats.Values.OrderByDescending(b => b.BidCount))
|
||||
{
|
||||
<tr class="transition-all">
|
||||
<td><strong>@bidder.Username</strong></td>
|
||||
<td><span class="badge bg-primary">@bidder.BidCount</span></td>
|
||||
<td>@bidder.LastBidTimeDisplay</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<hr class="my-3" />
|
||||
<h5 class="mb-3"><i class="bi bi-calculator-fill"></i> Valore Prodotto</h5>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 bg-light">
|
||||
<div class="card-body p-2 text-center">
|
||||
<small class="text-muted d-block">Prezzo Compra Subito</small>
|
||||
<strong class="d-block">@GetBuyNowPriceDisplay(selectedAuction)</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 bg-light">
|
||||
<div class="card-body p-2 text-center">
|
||||
<small class="text-muted d-block">Costo Totale</small>
|
||||
<strong class="d-block">@GetTotalCostDisplay(selectedAuction)</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 bg-light">
|
||||
<div class="card-body p-2 text-center">
|
||||
<small class="text-muted d-block">Risparmio</small>
|
||||
<strong class="d-block @GetSavingsClass(selectedAuction)">@GetSavingsDisplay(selectedAuction)</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 bg-light">
|
||||
<div class="card-body p-2 text-center">
|
||||
<small class="text-muted d-block">Conveniente?</small>
|
||||
<span class="@GetIsWorthItClass(selectedAuction) d-inline-block mt-1">
|
||||
@GetIsWorthItIcon(selectedAuction)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(selectedAuction.CalculatedValue.Summary))
|
||||
{
|
||||
<div class="alert alert-info mt-2 mb-0 p-2">
|
||||
<small><i class="bi bi-info-circle"></i> @selectedAuction.CalculatedValue.Summary</small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="auction-details animate-fade-in shadow-hover">
|
||||
<div class="alert alert-secondary text-center animate-pulse" style="margin-top: 50px;">
|
||||
<i class="bi bi-arrow-left animate-bounce" style="font-size: 3rem; display: block; margin-bottom: 1rem;"></i>
|
||||
<h4>Seleziona un'asta</h4>
|
||||
<p class="mb-0">Clicca su un'asta dalla lista per visualizzare i dettagli</p>
|
||||
<div class="auction-details animate-fade-in">
|
||||
<div class="alert alert-secondary text-center">
|
||||
<i class="bi bi-arrow-left" style="font-size: 2rem; display: block; margin-bottom: 0.5rem;"></i>
|
||||
<p class="mb-0">Seleziona un'asta per i dettagli</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="global-log mt-3 animate-fade-in-up delay-300">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0"><i class="bi bi-terminal"></i> Log Globale</h4>
|
||||
<button class="btn btn-sm btn-secondary hover-lift" @onclick="ClearGlobalLog">
|
||||
<i class="bi bi-trash"></i> Pulisci
|
||||
</button>
|
||||
</div>
|
||||
<div class="log-box">
|
||||
@if (globalLog.Count == 0)
|
||||
{
|
||||
<div class="text-muted"><i class="bi bi-inbox"></i> Nessun log ancora...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var logEntry in globalLog.TakeLast(100))
|
||||
{
|
||||
<div class="@GetLogEntryClass(logEntry)">@logEntry</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Aggiungi Asta -->
|
||||
@if (showAddDialog)
|
||||
{
|
||||
<div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content animate-scale-in">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-plus-circle"></i> Aggiungi Nuova Asta</h5>
|
||||
<button type="button" class="btn-close" @onclick="CloseAddDialog"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><i class="bi bi-link-45deg"></i> URL Asta:</label>
|
||||
<input type="text" class="form-control transition-colors @(addDialogError != null ? "is-invalid" : "")"
|
||||
@bind="addDialogUrl"
|
||||
placeholder="https://it.bidoo.com/asta/..." />
|
||||
@if (addDialogError != null)
|
||||
{
|
||||
<div class="invalid-feedback d-block animate-shake">
|
||||
<i class="bi bi-exclamation-triangle"></i> @addDialogError
|
||||
</div>
|
||||
}
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-info-circle"></i> Inserisci l'URL completo dell'asta da Bidoo.com
|
||||
</small>
|
||||
<!-- Modal Aggiungi Asta -->
|
||||
@if (showAddDialog)
|
||||
{
|
||||
<div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content animate-scale-in">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><i class="bi bi-plus-circle"></i> Aggiungi Nuova Asta</h5>
|
||||
<button type="button" class="btn-close" @onclick="CloseAddDialog"></button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><i class="bi bi-tag"></i> Nome Asta (opzionale):</label>
|
||||
<input type="text" class="form-control transition-colors" @bind="addDialogName" placeholder="Es: iPhone 15 Pro" />
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><i class="bi bi-link-45deg"></i> URL Asta:</label>
|
||||
<input type="text" class="form-control transition-colors @(addDialogError != null ? "is-invalid" : "")"
|
||||
@bind="addDialogUrl"
|
||||
placeholder="https://it.bidoo.com/asta/..." />
|
||||
@if (addDialogError != null)
|
||||
{
|
||||
<div class="invalid-feedback d-block animate-shake">
|
||||
<i class="bi bi-exclamation-triangle"></i> @addDialogError
|
||||
</div>
|
||||
}
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-info-circle"></i> Inserisci l'URL completo dell'asta da Bidoo.com
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold"><i class="bi bi-tag"></i> Nome Asta (opzionale):</label>
|
||||
<input type="text" class="form-control transition-colors" @bind="addDialogName" placeholder="Es: iPhone 15 Pro" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary hover-lift" @onclick="CloseAddDialog">
|
||||
<i class="bi bi-x-circle"></i> Annulla
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary hover-lift" @onclick="AddAuction" disabled="@string.IsNullOrWhiteSpace(addDialogUrl)">
|
||||
<i class="bi bi-plus-lg"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary hover-lift" @onclick="CloseAddDialog">
|
||||
<i class="bi bi-x-circle"></i> Annulla
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary hover-lift" @onclick="AddAuction" disabled="@string.IsNullOrWhiteSpace(addDialogUrl)">
|
||||
<i class="bi bi-plus-lg"></i> Aggiungi
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,10 @@ namespace AutoBidder.Pages
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
||||
|
||||
AddLog("Applicazione avviata");
|
||||
AddLog("? Applicazione avviata");
|
||||
|
||||
// Applica logica auto-start in base alle impostazioni
|
||||
ApplyAutoStartLogic();
|
||||
}
|
||||
|
||||
private void LoadAuctionsFromDisk()
|
||||
@@ -50,7 +53,78 @@ namespace AutoBidder.Pages
|
||||
|
||||
if (loadedAuctions.Count > 0)
|
||||
{
|
||||
AddLog($"Caricate {loadedAuctions.Count} aste salvate");
|
||||
AddLog($"?? Caricate {loadedAuctions.Count} aste salvate");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAutoStartLogic()
|
||||
{
|
||||
var settings = AutoBidder.Utilities.SettingsManager.Load();
|
||||
|
||||
if (settings.RememberAuctionStates)
|
||||
{
|
||||
// Modalità "Ricorda Stato": mantiene lo stato salvato di ogni asta
|
||||
var activeCount = auctions.Count(a => a.IsActive && !a.IsPaused);
|
||||
if (activeCount > 0)
|
||||
{
|
||||
AuctionMonitor.Start();
|
||||
isMonitoringActive = true;
|
||||
AddLog($"?? [REMEMBER STATE] Ripristinato stato salvato: {activeCount} aste attive");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog("?? [REMEMBER STATE] Nessuna asta attiva salvata");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Modalità "Default": applica DefaultStartAuctionsOnLoad a tutte le aste
|
||||
switch (settings.DefaultStartAuctionsOnLoad)
|
||||
{
|
||||
case "Active":
|
||||
// Avvia tutte le aste
|
||||
foreach (var auction in auctions)
|
||||
{
|
||||
auction.IsActive = true;
|
||||
auction.IsPaused = false;
|
||||
}
|
||||
if (auctions.Count > 0)
|
||||
{
|
||||
AuctionMonitor.Start();
|
||||
isMonitoringActive = true;
|
||||
SaveAuctions();
|
||||
AddLog($"?? [AUTO-START] Avviate automaticamente {auctions.Count} aste");
|
||||
}
|
||||
break;
|
||||
|
||||
case "Paused":
|
||||
// Mette in pausa tutte le aste
|
||||
foreach (var auction in auctions)
|
||||
{
|
||||
auction.IsActive = true;
|
||||
auction.IsPaused = true;
|
||||
}
|
||||
if (auctions.Count > 0)
|
||||
{
|
||||
AuctionMonitor.Start();
|
||||
isMonitoringActive = true;
|
||||
SaveAuctions();
|
||||
AddLog($"?? [AUTO-START] Aste in pausa: {auctions.Count}");
|
||||
}
|
||||
break;
|
||||
|
||||
case "Stopped":
|
||||
default:
|
||||
// Ferma tutte le aste (default)
|
||||
foreach (var auction in auctions)
|
||||
{
|
||||
auction.IsActive = false;
|
||||
auction.IsPaused = false;
|
||||
}
|
||||
SaveAuctions();
|
||||
AddLog($"?? [AUTO-START] Aste fermate all'avvio: {auctions.Count}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +151,9 @@ namespace AutoBidder.Pages
|
||||
var auction = auctions.FirstOrDefault(a => a.AuctionId == state.AuctionId);
|
||||
if (auction != null)
|
||||
{
|
||||
// Salva l'ultimo stato ricevuto
|
||||
auction.LastState = state;
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
@@ -129,10 +206,12 @@ namespace AutoBidder.Pages
|
||||
auction.IsActive = true;
|
||||
auction.IsPaused = false;
|
||||
|
||||
// Auto-start monitor se non attivo
|
||||
if (!isMonitoringActive)
|
||||
{
|
||||
AuctionMonitor.Start();
|
||||
isMonitoringActive = true;
|
||||
AddLog("[AUTO-START] Monitoraggio avviato");
|
||||
}
|
||||
|
||||
SaveAuctions();
|
||||
@@ -159,6 +238,14 @@ namespace AutoBidder.Pages
|
||||
auction.IsPaused = false;
|
||||
SaveAuctions();
|
||||
AddLog($"?? Fermata asta: {auction.Name}");
|
||||
|
||||
// Auto-stop monitor se nessuna asta è attiva
|
||||
if (!auctions.Any(a => a.IsActive))
|
||||
{
|
||||
AuctionMonitor.Stop();
|
||||
isMonitoringActive = false;
|
||||
AddLog("[AUTO-STOP] Monitoraggio fermato");
|
||||
}
|
||||
}
|
||||
|
||||
// Dialog Aggiungi Asta
|
||||
@@ -204,31 +291,75 @@ namespace AutoBidder.Pages
|
||||
// Carica impostazioni default
|
||||
var settings = AutoBidder.Utilities.SettingsManager.Load();
|
||||
|
||||
// Crea nuova asta
|
||||
// Determina stato iniziale in base a DefaultNewAuctionState
|
||||
bool isActive = false;
|
||||
bool isPaused = false;
|
||||
|
||||
switch (settings.DefaultNewAuctionState)
|
||||
{
|
||||
case "Active":
|
||||
isActive = true;
|
||||
isPaused = false;
|
||||
break;
|
||||
case "Paused":
|
||||
isActive = true;
|
||||
isPaused = true;
|
||||
break;
|
||||
case "Stopped":
|
||||
default:
|
||||
isActive = false;
|
||||
isPaused = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Crea nuova asta con nome temporaneo
|
||||
var tempName = string.IsNullOrWhiteSpace(addDialogName) ? $"Caricamento... (ID: {auctionId})" : addDialogName;
|
||||
|
||||
var newAuction = new AuctionInfo
|
||||
{
|
||||
AuctionId = auctionId,
|
||||
Name = string.IsNullOrWhiteSpace(addDialogName) ? $"Asta {auctionId}" : addDialogName,
|
||||
Name = tempName,
|
||||
OriginalUrl = addDialogUrl,
|
||||
BidBeforeDeadlineMs = settings.DefaultBidBeforeDeadlineMs,
|
||||
CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid,
|
||||
MinPrice = settings.DefaultMinPrice,
|
||||
MaxPrice = settings.DefaultMaxPrice,
|
||||
MaxClicks = settings.DefaultMaxClicks,
|
||||
IsActive = false,
|
||||
IsPaused = false
|
||||
MinResets = settings.DefaultMinResets,
|
||||
MaxResets = settings.DefaultMaxResets,
|
||||
IsActive = isActive,
|
||||
IsPaused = isPaused
|
||||
};
|
||||
|
||||
auctions.Add(newAuction);
|
||||
AuctionMonitor.AddAuction(newAuction);
|
||||
SaveAuctions();
|
||||
|
||||
AddLog($"? Aggiunta asta: {newAuction.Name} (ID: {auctionId})");
|
||||
// Log stato iniziale
|
||||
string stateLabel = settings.DefaultNewAuctionState switch
|
||||
{
|
||||
"Active" => "?? Attiva",
|
||||
"Paused" => "?? In Pausa",
|
||||
_ => "?? Fermata"
|
||||
};
|
||||
|
||||
AddLog($"? Aggiunta asta: {newAuction.Name} (ID: {auctionId}) - Stato: {stateLabel}");
|
||||
|
||||
// Auto-start monitor se necessario
|
||||
if (isActive && !isMonitoringActive)
|
||||
{
|
||||
AuctionMonitor.Start();
|
||||
isMonitoringActive = true;
|
||||
AddLog("[AUTO-START] Monitoraggio avviato per nuova asta attiva");
|
||||
}
|
||||
|
||||
CloseAddDialog();
|
||||
selectedAuction = newAuction;
|
||||
|
||||
// ?? TODO: Implementare caricamento nome e info prodotto in background
|
||||
// Richiede aggiunta metodo GetAuctionHtmlAsync a BidooApiClient
|
||||
}
|
||||
|
||||
|
||||
private string ExtractAuctionId(string url)
|
||||
{
|
||||
try
|
||||
@@ -309,36 +440,194 @@ namespace AutoBidder.Pages
|
||||
return "status-active";
|
||||
}
|
||||
|
||||
private string GetPriceDisplay(AuctionInfo auction)
|
||||
private string GetPriceDisplay(AuctionInfo? auction)
|
||||
{
|
||||
if (auction.CalculatedValue?.CurrentPrice > 0)
|
||||
return $"€{auction.CalculatedValue.CurrentPrice:F2}";
|
||||
try
|
||||
{
|
||||
if (auction == null) return "-";
|
||||
|
||||
// Prova a leggere da LastState prima
|
||||
if (auction.LastState != null && auction.LastState.Price > 0)
|
||||
return $"€{auction.LastState.Price:F2}";
|
||||
|
||||
// Fallback a CalculatedValue
|
||||
if (auction.CalculatedValue?.CurrentPrice > 0)
|
||||
return $"€{auction.CalculatedValue.CurrentPrice:F2}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] GetPriceDisplay: {ex.Message}");
|
||||
}
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetPriceClass(AuctionInfo auction)
|
||||
private string GetPriceClass(AuctionInfo? auction)
|
||||
{
|
||||
if (auction.CalculatedValue?.CurrentPrice > 0)
|
||||
return "fw-bold text-success";
|
||||
try
|
||||
{
|
||||
if (auction == null) return "text-muted";
|
||||
|
||||
double price = auction.LastState?.Price ?? auction.CalculatedValue?.CurrentPrice ?? 0;
|
||||
|
||||
if (price > 0)
|
||||
return "fw-bold text-success";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] GetPriceClass: {ex.Message}");
|
||||
}
|
||||
|
||||
return "text-muted";
|
||||
}
|
||||
|
||||
private string GetTimerDisplay(AuctionInfo auction)
|
||||
private string GetTimerDisplay(AuctionInfo? auction)
|
||||
{
|
||||
// TODO: Get from latest state
|
||||
try
|
||||
{
|
||||
if (auction == null) return "-";
|
||||
if (auction.LastState == null || auction.LastState.Timer <= 0)
|
||||
return "-";
|
||||
|
||||
var ts = TimeSpan.FromSeconds(auction.LastState.Timer);
|
||||
|
||||
if (ts.TotalHours >= 1)
|
||||
return $"{(int)ts.TotalHours}h {ts.Minutes}m";
|
||||
if (ts.TotalMinutes >= 1)
|
||||
return $"{ts.Minutes}m {ts.Seconds}s";
|
||||
|
||||
return $"{ts.Seconds}s";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] GetTimerDisplay: {ex.Message}");
|
||||
}
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetLastBidder(AuctionInfo auction)
|
||||
private string GetLastBidder(AuctionInfo? auction)
|
||||
{
|
||||
return auction.RecentBids.FirstOrDefault()?.Username ?? "-";
|
||||
try
|
||||
{
|
||||
if (auction == null) return "-";
|
||||
return auction.RecentBids?.FirstOrDefault()?.Username ?? "-";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
private int GetMyBidsCount(AuctionInfo auction)
|
||||
private int GetMyBidsCount(AuctionInfo? auction)
|
||||
{
|
||||
return auction.BidHistory.Count(b => b.EventType == BidEventType.MyBid);
|
||||
try
|
||||
{
|
||||
if (auction == null) return 0;
|
||||
return auction.BidHistory?.Count(b => b?.EventType == BidEventType.MyBid) ?? 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ?? NUOVI METODI: Visualizzazione valori prodotto
|
||||
|
||||
private string GetTotalCostDisplay(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.CalculatedValue != null)
|
||||
{
|
||||
return $"€{auction.CalculatedValue.TotalCostIfWin:F2}";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetSavingsDisplay(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.CalculatedValue?.Savings.HasValue == true)
|
||||
{
|
||||
var savings = auction.CalculatedValue.Savings.Value;
|
||||
var percentage = auction.CalculatedValue.SavingsPercentage ?? 0;
|
||||
|
||||
if (savings > 0)
|
||||
return $"+€{savings:F2} ({percentage:F0}%)";
|
||||
else
|
||||
return $"-€{Math.Abs(savings):F2} ({Math.Abs(percentage):F0}%)";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetSavingsClass(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.CalculatedValue?.Savings.HasValue == true)
|
||||
{
|
||||
return auction.CalculatedValue.Savings.Value > 0
|
||||
? "text-success fw-bold" // Verde per risparmio
|
||||
: "text-danger fw-bold"; // Rosso per perdita
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "text-muted";
|
||||
}
|
||||
|
||||
private string GetBuyNowPriceDisplay(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.BuyNowPrice.HasValue == true)
|
||||
{
|
||||
return $"€{auction.BuyNowPrice.Value:F2}";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetIsWorthItIcon(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.CalculatedValue != null)
|
||||
{
|
||||
return auction.CalculatedValue.IsWorthIt ? "?" : "?";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "-";
|
||||
}
|
||||
|
||||
private string GetIsWorthItClass(AuctionInfo? auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (auction?.CalculatedValue != null)
|
||||
{
|
||||
return auction.CalculatedValue.IsWorthIt
|
||||
? "badge bg-success"
|
||||
: "badge bg-danger";
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "badge bg-secondary";
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAuctionLog(AuctionInfo auction)
|
||||
{
|
||||
return auction.AuctionLog.TakeLast(50);
|
||||
@@ -346,11 +635,18 @@ namespace AutoBidder.Pages
|
||||
|
||||
private string GetLogEntryClass(string logEntry)
|
||||
{
|
||||
if (logEntry.Contains("?") || logEntry.Contains("Errore") || logEntry.Contains("errore"))
|
||||
return "log-entry-error";
|
||||
if (logEntry.Contains("??") || logEntry.Contains("Warning") || logEntry.Contains("warning"))
|
||||
return "log-entry-warning";
|
||||
return "log-entry-new";
|
||||
try
|
||||
{
|
||||
if (logEntry.Contains("[ERROR]") || logEntry.Contains("Errore") || logEntry.Contains("errore") || logEntry.Contains("FAIL"))
|
||||
return "log-entry-error";
|
||||
if (logEntry.Contains("[WARN]") || logEntry.Contains("Warning") || logEntry.Contains("warning"))
|
||||
return "log-entry-warning";
|
||||
if (logEntry.Contains("[OK]") || logEntry.Contains("SUCCESS") || logEntry.Contains("Vinta"))
|
||||
return "log-entry-success";
|
||||
}
|
||||
catch { }
|
||||
|
||||
return "log-entry-info";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@inject SessionService SessionService
|
||||
@inject AuctionMonitor AuctionMonitor
|
||||
@inject IJSRuntime JSRuntime
|
||||
@implements IDisposable
|
||||
|
||||
<PageTitle>Impostazioni - AutoBidder</PageTitle>
|
||||
|
||||
@@ -11,6 +12,7 @@
|
||||
<h2 class="mb-0 fw-bold">Impostazioni</h2>
|
||||
</div>
|
||||
|
||||
<!-- SESSIONE BIDOO -->
|
||||
<div class="card mb-4 shadow-hover animate-fade-in-up delay-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-wifi"></i> Sessione Bidoo</h5>
|
||||
@@ -54,7 +56,7 @@
|
||||
placeholder="Incolla qui il cookie di sessione da browser..."></textarea>
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-info-circle"></i> Apri gli strumenti sviluppatore del browser (F12), vai alla tab "Network",
|
||||
visita bidoo.com, copia il valore del cookie "__stattrb" o simile.
|
||||
visita bidoo.com, copia il valore del cookie.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +74,7 @@
|
||||
<button class="btn btn-primary hover-lift" @onclick="Connect" disabled="@(string.IsNullOrEmpty(cookieInput) || isConnecting)">
|
||||
@if (isConnecting)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-2 animate-spin"></span>
|
||||
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||
<span>Connessione...</span>
|
||||
}
|
||||
else
|
||||
@@ -85,6 +87,102 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMPORTAMENTO AVVIO ASTE -->
|
||||
<div class="card mb-4 shadow-hover animate-fade-in-up delay-150">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h5 class="mb-0"><i class="bi bi-power"></i> Comportamento Avvio Aste</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-folder-open"></i> Stato Aste al Caricamento:
|
||||
</label>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="loadState" id="loadRemember"
|
||||
checked="@settings.RememberAuctionStates" @onclick="@(() => SetRememberState(true))" />
|
||||
<label class="form-check-label" for="loadRemember">
|
||||
<i class="bi bi-memory"></i> <strong>Ricorda Stato</strong> - Mantiene lo stato salvato
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="loadState" id="loadActive"
|
||||
checked="@(!settings.RememberAuctionStates && settings.DefaultStartAuctionsOnLoad == "Active")"
|
||||
@onclick="@(() => SetLoadState("Active"))" />
|
||||
<label class="form-check-label" for="loadActive">
|
||||
<i class="bi bi-play-circle-fill text-success"></i> <strong>Avvia Tutte</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="loadState" id="loadPaused"
|
||||
checked="@(!settings.RememberAuctionStates && settings.DefaultStartAuctionsOnLoad == "Paused")"
|
||||
@onclick="@(() => SetLoadState("Paused"))" />
|
||||
<label class="form-check-label" for="loadPaused">
|
||||
<i class="bi bi-pause-circle-fill text-warning"></i> <strong>In Pausa</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="loadState" id="loadStopped"
|
||||
checked="@(!settings.RememberAuctionStates && settings.DefaultStartAuctionsOnLoad == "Stopped")"
|
||||
@onclick="@(() => SetLoadState("Stopped"))" />
|
||||
<label class="form-check-label" for="loadStopped">
|
||||
<i class="bi bi-stop-circle-fill text-danger"></i> <strong>Fermate</strong> (default)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-plus-circle"></i> Stato Nuove Aste:
|
||||
</label>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="newState" id="newActive"
|
||||
checked="@(settings.DefaultNewAuctionState == "Active")"
|
||||
@onclick="@(() => SetNewAuctionState("Active"))" />
|
||||
<label class="form-check-label" for="newActive">
|
||||
<i class="bi bi-play-circle-fill text-success"></i> <strong>Attiva</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="radio" name="newState" id="newPaused"
|
||||
checked="@(settings.DefaultNewAuctionState == "Paused")"
|
||||
@onclick="@(() => SetNewAuctionState("Paused"))" />
|
||||
<label class="form-check-label" for="newPaused">
|
||||
<i class="bi bi-pause-circle-fill text-warning"></i> <strong>In Pausa</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="newState" id="newStopped"
|
||||
checked="@(settings.DefaultNewAuctionState == "Stopped")"
|
||||
@onclick="@(() => SetNewAuctionState("Stopped"))" />
|
||||
<label class="form-check-label" for="newStopped">
|
||||
<i class="bi bi-stop-circle-fill text-danger"></i> <strong>Fermata</strong> (default)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info border-0 shadow-sm mt-3">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-lightbulb-fill me-3 mt-1" style="font-size: 1.5rem;"></i>
|
||||
<div>
|
||||
<strong>Raccomandazioni:</strong>
|
||||
<ul class="mb-0 mt-2">
|
||||
<li><strong>Principianti:</strong> Usa "Fermate" - configura prima di avviare</li>
|
||||
<li><strong>Intermedi:</strong> Usa "In Pausa" - monitora senza puntare</li>
|
||||
<li><strong>Avanzati:</strong> Usa "Ricorda Stato" se configurato</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-warning text-dark hover-lift" @onclick="SaveSettings">
|
||||
<i class="bi bi-check-lg"></i> Salva Comportamento Avvio
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMPOSTAZIONI PREDEFINITE ASTE -->
|
||||
<div class="card mb-4 shadow-hover animate-fade-in-up delay-200">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-sliders"></i> Impostazioni Predefinite Aste</h5>
|
||||
@@ -95,43 +193,63 @@
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-speedometer2"></i> Anticipo Puntata (ms):
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.DefaultBidBeforeDeadlineMs" />
|
||||
<input type="number" class="form-control" @bind="settings.DefaultBidBeforeDeadlineMs" />
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-clock"></i> Millisecondi prima della scadenza per inviare la puntata
|
||||
<i class="bi bi-clock"></i> Millisecondi prima della scadenza
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-currency-euro"></i> Prezzo Minimo (€):
|
||||
</label>
|
||||
<input type="number" step="0.01" class="form-control transition-colors" @bind="settings.DefaultMinPrice" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-currency-euro"></i> Prezzo Massimo (€):
|
||||
</label>
|
||||
<input type="number" step="0.01" class="form-control transition-colors" @bind="settings.DefaultMaxPrice" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-hand-index-thumb"></i> Click Massimi:
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.DefaultMaxClicks" />
|
||||
<input type="number" class="form-control" @bind="settings.DefaultMaxClicks" />
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-infinity"></i> 0 = illimitati
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-currency-euro"></i> Prezzo Minimo (€):
|
||||
</label>
|
||||
<input type="number" step="0.01" class="form-control" @bind="settings.DefaultMinPrice" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-currency-euro"></i> Prezzo Massimo (€):
|
||||
</label>
|
||||
<input type="number" step="0.01" class="form-control" @bind="settings.DefaultMaxPrice" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-arrow-repeat"></i> Reset Minimi:
|
||||
</label>
|
||||
<input type="number" class="form-control" @bind="settings.DefaultMinResets" />
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-clock"></i> Punta solo se almeno X reset (0 = ignora)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-arrow-repeat"></i> Reset Massimi:
|
||||
</label>
|
||||
<input type="number" class="form-control" @bind="settings.DefaultMaxResets" />
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-stop-circle"></i> Ferma dopo X reset (0 = illimitati)
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-shield-check"></i> Puntate Minime da Mantenere:
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.MinimumRemainingBids" />
|
||||
<input type="number" class="form-control" @bind="settings.MinimumRemainingBids" />
|
||||
<small class="form-text text-muted">
|
||||
<i class="bi bi-lock"></i> Blocca puntate automatiche sotto questo limite
|
||||
<i class="bi bi-lock"></i> Blocca puntate sotto questo limite
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@@ -139,7 +257,7 @@
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="checkAuction" @bind="settings.DefaultCheckAuctionOpenBeforeBid" />
|
||||
<label class="form-check-label" for="checkAuction">
|
||||
<i class="bi bi-shield-fill-check"></i> Verifica asta aperta prima di puntare (default)
|
||||
<i class="bi bi-shield-fill-check"></i> Verifica asta aperta prima di puntare
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -151,6 +269,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LIMITI LOG -->
|
||||
<div class="card shadow-hover animate-fade-in-up delay-300">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-journal-text"></i> Limiti Log</h5>
|
||||
@@ -161,26 +280,35 @@
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-list-ul"></i> Righe Log Globale:
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.MaxGlobalLogLines" />
|
||||
<input type="number" class="form-control" @bind="settings.MaxGlobalLogLines" />
|
||||
<small class="form-text text-muted">
|
||||
Numero massimo di righe nel log principale
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-list-check"></i> Righe Log per Asta:
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.MaxLogLinesPerAuction" />
|
||||
<input type="number" class="form-control" @bind="settings.MaxLogLinesPerAuction" />
|
||||
<small class="form-text text-muted">
|
||||
Numero massimo di righe per ogni asta
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
<i class="bi bi-clock-history"></i> Voci Storia Puntate:
|
||||
</label>
|
||||
<input type="number" class="form-control transition-colors" @bind="settings.MaxBidHistoryEntries" />
|
||||
<input type="number" class="form-control" @bind="settings.MaxBidHistoryEntries" />
|
||||
<small class="form-text text-muted">
|
||||
Numero massimo di puntate da mantenere (0 = illimitate)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-info text-white hover-lift" @onclick="SaveSettings">
|
||||
<i class="bi bi-check-lg"></i> Salva Impostazioni
|
||||
<i class="bi bi-check-lg"></i> Salva Limiti Log
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,22 +319,6 @@
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
padding: 1.25rem;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
@@ -224,7 +336,6 @@
|
||||
LoadSession();
|
||||
LoadSettings();
|
||||
|
||||
// Auto-refresh dati utente ogni 30 secondi
|
||||
updateTimer = new System.Threading.Timer(async _ =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(currentUsername))
|
||||
@@ -237,7 +348,6 @@
|
||||
|
||||
private void LoadSession()
|
||||
{
|
||||
// Carica sessione salvata
|
||||
var savedSession = AutoBidder.Services.SessionManager.LoadSession();
|
||||
if (savedSession != null && savedSession.IsValid)
|
||||
{
|
||||
@@ -245,7 +355,6 @@
|
||||
remainingBids = savedSession.RemainingBids;
|
||||
cookieInput = savedSession.CookieString ?? "";
|
||||
|
||||
// Inizializza il monitor con la sessione
|
||||
if (!string.IsNullOrEmpty(savedSession.CookieString))
|
||||
{
|
||||
AuctionMonitor.InitializeSessionWithCookie(savedSession.CookieString, savedSession.Username ?? "");
|
||||
@@ -253,7 +362,6 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prova a caricare da AuctionMonitor
|
||||
var session = AuctionMonitor.GetSession();
|
||||
currentUsername = session?.Username;
|
||||
remainingBids = session?.RemainingBids ?? 0;
|
||||
@@ -283,7 +391,6 @@
|
||||
var session = AuctionMonitor.GetSession();
|
||||
if (session != null && !string.IsNullOrEmpty(session.Username))
|
||||
{
|
||||
// Salva la sessione
|
||||
AutoBidder.Services.SessionManager.SaveSession(session);
|
||||
|
||||
LoadSession();
|
||||
@@ -291,21 +398,21 @@
|
||||
usernameInput = "";
|
||||
connectionError = null;
|
||||
|
||||
await JSRuntime.InvokeVoidAsync("alert", $"? Connesso con successo come {session.Username}!");
|
||||
await JSRuntime.InvokeVoidAsync("alert", $"? Connesso come {session.Username}!");
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionError = "Sessione creata ma dati utente non disponibili. Verifica il cookie.";
|
||||
connectionError = "Sessione creata ma dati utente non disponibili.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
connectionError = "Impossibile connettersi. Verifica che il cookie sia corretto e non scaduto.";
|
||||
connectionError = "Impossibile connettersi. Verifica il cookie.";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
connectionError = $"Errore durante la connessione: {ex.Message}";
|
||||
connectionError = $"Errore: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -334,16 +441,11 @@
|
||||
{
|
||||
currentUsername = session.Username;
|
||||
remainingBids = session.RemainingBids;
|
||||
|
||||
// Aggiorna sessione salvata
|
||||
AutoBidder.Services.SessionManager.SaveSession(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignora errori di refresh silenziosamente
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
@@ -351,6 +453,26 @@
|
||||
AutoBidder.Utilities.SettingsManager.Save(settings);
|
||||
_ = JSRuntime.InvokeVoidAsync("alert", "? Impostazioni salvate con successo!");
|
||||
}
|
||||
|
||||
private void SetRememberState(bool remember)
|
||||
{
|
||||
settings.RememberAuctionStates = remember;
|
||||
if (remember)
|
||||
{
|
||||
settings.DefaultStartAuctionsOnLoad = "Stopped";
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLoadState(string state)
|
||||
{
|
||||
settings.RememberAuctionStates = false;
|
||||
settings.DefaultStartAuctionsOnLoad = state;
|
||||
}
|
||||
|
||||
private void SetNewAuctionState(string state)
|
||||
{
|
||||
settings.DefaultNewAuctionState = state;
|
||||
}
|
||||
|
||||
private string GetRemainingBidsClass()
|
||||
{
|
||||
@@ -364,5 +486,3 @@
|
||||
updateTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@implements IDisposable
|
||||
|
||||
@@ -172,7 +172,16 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshStats();
|
||||
try
|
||||
{
|
||||
await RefreshStats();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Errore inizializzazione: {ex.Message}";
|
||||
stats = new List<ProductStat>();
|
||||
Console.WriteLine($"[ERROR] Statistics OnInitializedAsync: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshStats()
|
||||
@@ -183,13 +192,27 @@
|
||||
errorMessage = null;
|
||||
StateHasChanged();
|
||||
|
||||
// Tentativo caricamento con timeout
|
||||
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
stats = await StatsService.GetAllStatsAsync();
|
||||
|
||||
if (stats == null)
|
||||
{
|
||||
stats = new List<ProductStat>();
|
||||
errorMessage = "Nessuna statistica disponibile. Il database potrebbe non essere inizializzato.";
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
errorMessage = "Timeout durante caricamento statistiche. Riprova più tardi.";
|
||||
stats = new List<ProductStat>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Si è verificato un errore: {ex.Message}";
|
||||
stats = new List<ProductStat>();
|
||||
Console.WriteLine($"[ERROR] Statistics page: {ex}");
|
||||
Console.WriteLine($"[ERROR] Statistics RefreshStats: {ex}");
|
||||
Console.WriteLine($"[ERROR] Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
<base href="~/" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="css/app-wpf.css" rel="stylesheet" />
|
||||
<link href="css/animations.css" rel="stylesheet" />
|
||||
<link href="AutoBidder.styles.css" rel="stylesheet" />
|
||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -27,11 +26,10 @@
|
||||
Si è verificato un errore non gestito. Consultare la console del browser per ulteriori informazioni.
|
||||
</environment>
|
||||
<a href="" class="reload">Ricarica</a>
|
||||
<a class="dismiss">??</a>
|
||||
<a class="dismiss">?</a>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="js/browser-interop.js"></script>
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet" />
|
||||
<link href="css/app.css" rel="stylesheet" />
|
||||
<link href="css/animations.css" rel="stylesheet" />
|
||||
<link href="AutoBidder.styles.css" rel="stylesheet" />
|
||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -23,7 +22,7 @@
|
||||
Si è verificato un errore non gestito. Consultare la console del browser per ulteriori informazioni.
|
||||
</environment>
|
||||
<a href="" class="reload">Ricarica</a>
|
||||
<a class="dismiss">??</a>
|
||||
<a class="dismiss">?</a>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using System.Data.Common;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -81,6 +82,7 @@ htmlCacheService.OnLog += (msg) => Console.WriteLine(msg);
|
||||
builder.Services.AddSingleton(auctionMonitor);
|
||||
builder.Services.AddSingleton(htmlCacheService);
|
||||
builder.Services.AddSingleton(sp => new SessionService(auctionMonitor.GetApiClient()));
|
||||
builder.Services.AddSingleton<DatabaseService>();
|
||||
builder.Services.AddScoped<StatsService>(sp =>
|
||||
{
|
||||
var ctx = sp.GetRequiredService<StatisticsContext>();
|
||||
@@ -97,11 +99,82 @@ builder.Services.AddSignalR(options =>
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Crea database se non esiste (senza migrations)
|
||||
// ??? NUOVO: Inizializza DatabaseService
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var databaseService = scope.ServiceProvider.GetRequiredService<DatabaseService>();
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("[DB] Initializing main database...");
|
||||
await databaseService.InitializeDatabaseAsync();
|
||||
|
||||
var dbInfo = await databaseService.GetDatabaseInfoAsync();
|
||||
Console.WriteLine($"[DB] Database initialized successfully:");
|
||||
Console.WriteLine($"[DB] Path: {dbInfo.Path}");
|
||||
Console.WriteLine($"[DB] Size: {dbInfo.SizeFormatted}");
|
||||
Console.WriteLine($"[DB] Version: {dbInfo.Version}");
|
||||
Console.WriteLine($"[DB] Auctions: {dbInfo.AuctionsCount}");
|
||||
Console.WriteLine($"[DB] Bid History: {dbInfo.BidHistoryCount}");
|
||||
Console.WriteLine($"[DB] Product Stats: {dbInfo.ProductStatsCount}");
|
||||
|
||||
// Verifica salute database
|
||||
var isHealthy = await databaseService.CheckDatabaseHealthAsync();
|
||||
Console.WriteLine($"[DB] Database health check: {(isHealthy ? "OK" : "FAILED")}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DB ERROR] Failed to initialize database: {ex.Message}");
|
||||
Console.WriteLine($"[DB ERROR] Stack trace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
// Crea database statistiche se non esiste (senza migrations)
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<StatisticsContext>();
|
||||
db.Database.EnsureCreated(); // Crea schema automaticamente se non esiste
|
||||
|
||||
try
|
||||
{
|
||||
// Log percorso database
|
||||
var connection = db.Database.GetDbConnection();
|
||||
Console.WriteLine($"[STATS DB] Database path: {connection.DataSource}");
|
||||
|
||||
// Verifica se database esiste
|
||||
var dbExists = db.Database.CanConnect();
|
||||
Console.WriteLine($"[STATS DB] Database exists: {dbExists}");
|
||||
|
||||
// Forza creazione tabelle se non esistono
|
||||
if (!dbExists || !db.ProductStats.Any())
|
||||
{
|
||||
Console.WriteLine("[STATS DB] Creating database schema...");
|
||||
db.Database.EnsureDeleted(); // Elimina database vecchio
|
||||
db.Database.EnsureCreated(); // Ricrea con schema aggiornato
|
||||
Console.WriteLine("[STATS DB] Database schema created successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[STATS DB] Database already exists with {db.ProductStats.Count()} records");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[STATS DB ERROR] Failed to initialize database: {ex.Message}");
|
||||
Console.WriteLine($"[STATS DB ERROR] Stack trace: {ex.StackTrace}");
|
||||
|
||||
// Prova a ricreare forzatamente
|
||||
try
|
||||
{
|
||||
Console.WriteLine("[STATS DB] Attempting forced recreation...");
|
||||
db.Database.EnsureDeleted();
|
||||
db.Database.EnsureCreated();
|
||||
Console.WriteLine("[STATS DB] Forced recreation successful");
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Console.WriteLine($"[STATS DB ERROR] Forced recreation failed: {ex2.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the HTTP request pipeline
|
||||
|
||||
@@ -298,6 +298,39 @@ namespace AutoBidder.Services
|
||||
OnAuctionUpdated?.Invoke(state);
|
||||
UpdateAuctionHistory(auction, state);
|
||||
|
||||
// ?? NUOVO: Calcola e aggiorna valore prodotto se disponibili dati
|
||||
if (auction.BuyNowPrice.HasValue || auction.ShippingCost.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var productValue = Utilities.ProductValueCalculator.Calculate(
|
||||
auction,
|
||||
state.Price,
|
||||
auction.RecentBids?.Count ?? 0
|
||||
);
|
||||
|
||||
auction.CalculatedValue = productValue;
|
||||
|
||||
// Log valore solo se cambia significativamente o ogni 10 polling
|
||||
bool shouldLogValue = false;
|
||||
if (auction.PollingLatencyMs % 10 == 0) // Ogni ~10 poll
|
||||
{
|
||||
shouldLogValue = true;
|
||||
}
|
||||
|
||||
if (shouldLogValue && productValue.Savings.HasValue)
|
||||
{
|
||||
var valueMsg = Utilities.ProductValueCalculator.FormatValueMessage(productValue);
|
||||
auction.AddLog($"[VALUE] {valueMsg}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Silenzioso - non vogliamo bloccare il polling per errori di calcolo
|
||||
auction.AddLog($"[WARN] Errore calcolo valore: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// NUOVA LOGICA: Punta solo se siamo vicini alla deadline E nessun altro ha appena puntato
|
||||
if (state.Status == AuctionStatus.Running && !auction.IsPaused && !auction.IsAttackInProgress)
|
||||
{
|
||||
@@ -439,51 +472,81 @@ namespace AutoBidder.Services
|
||||
|
||||
private bool ShouldBid(AuctionInfo auction, AuctionState state)
|
||||
{
|
||||
// ? NUOVO: Controllo limite minimo puntate residue
|
||||
var settings = Utilities.SettingsManager.Load();
|
||||
if (settings.MinimumRemainingBids > 0)
|
||||
// ?? CONTROLLO 0: Verifica convenienza (se dati disponibili)
|
||||
if (auction.CalculatedValue != null &&
|
||||
auction.CalculatedValue.Savings.HasValue &&
|
||||
!auction.CalculatedValue.IsWorthIt)
|
||||
{
|
||||
// Ottieni puntate residue dalla sessione
|
||||
var session = _apiClient.GetSession();
|
||||
if (session != null && session.RemainingBids <= settings.MinimumRemainingBids)
|
||||
// Permetti comunque di puntare se il risparmio è ancora positivo (anche se piccolo)
|
||||
// Blocca solo se sta andando in perdita significativa (< -5%)
|
||||
if (auction.CalculatedValue.SavingsPercentage.HasValue &&
|
||||
auction.CalculatedValue.SavingsPercentage.Value < -5)
|
||||
{
|
||||
auction.AddLog($"[LIMIT] Puntata bloccata: puntate residue ({session.RemainingBids}) al limite minimo ({settings.MinimumRemainingBids})");
|
||||
auction.AddLog($"[VALUE] Puntata bloccata: perdita {auction.CalculatedValue.SavingsPercentage.Value:F1}% (troppo costoso)");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ? NUOVO: Non puntare se sono già il vincitore corrente
|
||||
// ??? CONTROLLO 1: Limite minimo puntate residue
|
||||
var settings = Utilities.SettingsManager.Load();
|
||||
if (settings.MinimumRemainingBids > 0)
|
||||
{
|
||||
var session = _apiClient.GetSession();
|
||||
if (session != null && session.RemainingBids <= settings.MinimumRemainingBids)
|
||||
{
|
||||
auction.AddLog($"[LIMIT] Puntata bloccata: puntate residue ({session.RemainingBids}) <= limite ({settings.MinimumRemainingBids})");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ? CONTROLLO 2: Non puntare se sono già il vincitore corrente
|
||||
if (state.IsMyBid)
|
||||
{
|
||||
// Sono già io l'ultimo ad aver puntato, non serve puntare di nuovo
|
||||
return false;
|
||||
}
|
||||
|
||||
// Price check
|
||||
// ?? CONTROLLO 3: MinPrice/MaxPrice
|
||||
if (auction.MinPrice > 0 && state.Price < auction.MinPrice)
|
||||
{
|
||||
auction.AddLog($"[PRICE] Prezzo troppo basso: €{state.Price:F2} < Min €{auction.MinPrice:F2}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auction.MaxPrice > 0 && state.Price > auction.MaxPrice)
|
||||
{
|
||||
auction.AddLog($"[PRICE] Prezzo troppo alto: €{state.Price:F2} > Max €{auction.MaxPrice:F2}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset count check
|
||||
// ?? CONTROLLO 4: MinResets/MaxResets
|
||||
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
|
||||
{
|
||||
auction.AddLog($"[RESET] Reset troppo bassi: {auction.ResetCount} < Min {auction.MinResets}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auction.MaxResets > 0 && auction.ResetCount >= auction.MaxResets)
|
||||
{
|
||||
auction.AddLog($"[RESET] Reset massimi raggiunti: {auction.ResetCount} >= Max {auction.MaxResets}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Max clicks check
|
||||
// ??? CONTROLLO 5: MaxClicks
|
||||
int myBidsCount = auction.BidHistory.Count(b => b.EventType == BidEventType.MyBid);
|
||||
if (auction.MaxClicks > 0 && myBidsCount >= auction.MaxClicks)
|
||||
{
|
||||
auction.AddLog($"[CLICKS] Click massimi raggiunti: {myBidsCount} >= Max {auction.MaxClicks}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cooldown check (evita puntate multiple ravvicinate)
|
||||
// ?? CONTROLLO 6: Cooldown (evita puntate multiple ravvicinate)
|
||||
if (auction.LastClickAt.HasValue)
|
||||
{
|
||||
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
|
||||
if (timeSinceLastClick.TotalMilliseconds < 800)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
414
Mimante/Services/DatabaseService.cs
Normal file
414
Mimante/Services/DatabaseService.cs
Normal file
@@ -0,0 +1,414 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AutoBidder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio per gestione database SQLite con auto-creazione tabelle e migrations
|
||||
/// </summary>
|
||||
public class DatabaseService : IDisposable
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _databasePath;
|
||||
private SqliteConnection? _connection;
|
||||
|
||||
public DatabaseService()
|
||||
{
|
||||
// Crea directory data se non esiste
|
||||
var dataDir = Path.Combine(AppContext.BaseDirectory, "data");
|
||||
Directory.CreateDirectory(dataDir);
|
||||
|
||||
_databasePath = Path.Combine(dataDir, "autobidder.db");
|
||||
_connectionString = $"Data Source={_databasePath}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializza il database creando le tabelle se necessario
|
||||
/// </summary>
|
||||
public async Task InitializeDatabaseAsync()
|
||||
{
|
||||
await using var connection = new SqliteConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Abilita foreign keys
|
||||
await using (var cmd = connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = "PRAGMA foreign_keys = ON;";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
// Crea tabelle se non esistono
|
||||
await CreateTablesAsync(connection);
|
||||
|
||||
// Esegui migrations
|
||||
await RunMigrationsAsync(connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea tutte le tabelle necessarie
|
||||
/// </summary>
|
||||
private async Task CreateTablesAsync(SqliteConnection connection)
|
||||
{
|
||||
var createTablesSql = @"
|
||||
-- Tabella versione database per migrations
|
||||
CREATE TABLE IF NOT EXISTS DatabaseVersion (
|
||||
Version INTEGER PRIMARY KEY,
|
||||
AppliedAt TEXT NOT NULL,
|
||||
Description TEXT
|
||||
);
|
||||
|
||||
-- Tabella aste monitorate
|
||||
CREATE TABLE IF NOT EXISTS Auctions (
|
||||
AuctionId TEXT PRIMARY KEY,
|
||||
Name TEXT NOT NULL,
|
||||
OriginalUrl TEXT NOT NULL,
|
||||
BidBeforeDeadlineMs INTEGER NOT NULL DEFAULT 200,
|
||||
CheckAuctionOpenBeforeBid INTEGER NOT NULL DEFAULT 0,
|
||||
MinPrice REAL NOT NULL DEFAULT 0,
|
||||
MaxPrice REAL NOT NULL DEFAULT 0,
|
||||
MinResets INTEGER NOT NULL DEFAULT 0,
|
||||
MaxResets INTEGER NOT NULL DEFAULT 0,
|
||||
MaxClicks INTEGER NOT NULL DEFAULT 0,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||
IsPaused INTEGER NOT NULL DEFAULT 0,
|
||||
ResetCount INTEGER NOT NULL DEFAULT 0,
|
||||
RemainingBids INTEGER,
|
||||
BidsUsedOnThisAuction INTEGER,
|
||||
BuyNowPrice REAL,
|
||||
ShippingCost REAL,
|
||||
HasWinLimit INTEGER NOT NULL DEFAULT 0,
|
||||
WinLimitDescription TEXT,
|
||||
BidCost REAL NOT NULL DEFAULT 0.20,
|
||||
AddedAt TEXT NOT NULL,
|
||||
LastClickAt TEXT,
|
||||
CreatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UpdatedAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Tabella storia puntate
|
||||
CREATE TABLE IF NOT EXISTS BidHistory (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
AuctionId TEXT NOT NULL,
|
||||
Timestamp TEXT NOT NULL,
|
||||
EventType INTEGER NOT NULL,
|
||||
Bidder TEXT,
|
||||
Price REAL NOT NULL,
|
||||
Timer REAL NOT NULL,
|
||||
LatencyMs INTEGER,
|
||||
Success INTEGER,
|
||||
Notes TEXT,
|
||||
FOREIGN KEY (AuctionId) REFERENCES Auctions(AuctionId) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tabella statistiche puntatori
|
||||
CREATE TABLE IF NOT EXISTS BidderStats (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
AuctionId TEXT NOT NULL,
|
||||
Username TEXT NOT NULL,
|
||||
BidCount INTEGER NOT NULL DEFAULT 0,
|
||||
LastBidTime TEXT,
|
||||
FOREIGN KEY (AuctionId) REFERENCES Auctions(AuctionId) ON DELETE CASCADE,
|
||||
UNIQUE(AuctionId, Username)
|
||||
);
|
||||
|
||||
-- Tabella log aste
|
||||
CREATE TABLE IF NOT EXISTS AuctionLogs (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
AuctionId TEXT NOT NULL,
|
||||
Timestamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
LogLevel TEXT NOT NULL,
|
||||
Message TEXT NOT NULL,
|
||||
FOREIGN KEY (AuctionId) REFERENCES Auctions(AuctionId) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Tabella statistiche prodotti (già esistente da StatsService)
|
||||
CREATE TABLE IF NOT EXISTS ProductStats (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ProductKey TEXT NOT NULL,
|
||||
ProductName TEXT NOT NULL,
|
||||
TotalAuctions INTEGER NOT NULL DEFAULT 0,
|
||||
TotalBidsUsed INTEGER NOT NULL DEFAULT 0,
|
||||
TotalFinalPriceCents INTEGER NOT NULL DEFAULT 0,
|
||||
LastSeen TEXT NOT NULL,
|
||||
UNIQUE(ProductKey)
|
||||
);
|
||||
|
||||
-- Indici per performance
|
||||
CREATE INDEX IF NOT EXISTS idx_auctions_isactive ON Auctions(IsActive);
|
||||
CREATE INDEX IF NOT EXISTS idx_bidhistory_auctionid ON BidHistory(AuctionId);
|
||||
CREATE INDEX IF NOT EXISTS idx_bidhistory_timestamp ON BidHistory(Timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_bidderstats_auctionid ON BidderStats(AuctionId);
|
||||
CREATE INDEX IF NOT EXISTS idx_auctionlogs_auctionid ON AuctionLogs(AuctionId);
|
||||
CREATE INDEX IF NOT EXISTS idx_auctionlogs_timestamp ON AuctionLogs(Timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_productstats_productkey ON ProductStats(ProductKey);
|
||||
";
|
||||
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = createTablesSql;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue migrations del database
|
||||
/// </summary>
|
||||
private async Task RunMigrationsAsync(SqliteConnection connection)
|
||||
{
|
||||
var currentVersion = await GetDatabaseVersionAsync(connection);
|
||||
|
||||
// Migrations in ordine crescente
|
||||
var migrations = new[]
|
||||
{
|
||||
new Migration(1, "Initial schema", async (conn) => {
|
||||
// Schema già creato in CreateTablesAsync
|
||||
await Task.CompletedTask;
|
||||
}),
|
||||
|
||||
new Migration(2, "Add UpdatedAt triggers", async (conn) => {
|
||||
var sql = @"
|
||||
-- Trigger per aggiornare UpdatedAt automaticamente
|
||||
CREATE TRIGGER IF NOT EXISTS update_auctions_timestamp
|
||||
AFTER UPDATE ON Auctions
|
||||
BEGIN
|
||||
UPDATE Auctions SET UpdatedAt = CURRENT_TIMESTAMP WHERE AuctionId = NEW.AuctionId;
|
||||
END;
|
||||
";
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}),
|
||||
|
||||
new Migration(3, "Add BidHistory indexes optimization", async (conn) => {
|
||||
var sql = @"
|
||||
CREATE INDEX IF NOT EXISTS idx_bidhistory_composite ON BidHistory(AuctionId, Timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_bidderstats_composite ON BidderStats(AuctionId, Username);
|
||||
";
|
||||
await using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
})
|
||||
};
|
||||
|
||||
foreach (var migration in migrations)
|
||||
{
|
||||
if (migration.Version > currentVersion)
|
||||
{
|
||||
await migration.Execute(connection);
|
||||
await SetDatabaseVersionAsync(connection, migration.Version, migration.Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene la versione corrente del database
|
||||
/// </summary>
|
||||
private async Task<int> GetDatabaseVersionAsync(SqliteConnection connection)
|
||||
{
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT COALESCE(MAX(Version), 0) FROM DatabaseVersion;";
|
||||
var result = await cmd.ExecuteScalarAsync();
|
||||
return Convert.ToInt32(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imposta la versione del database dopo una migration
|
||||
/// </summary>
|
||||
private async Task SetDatabaseVersionAsync(SqliteConnection connection, int version, string description)
|
||||
{
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
INSERT INTO DatabaseVersion (Version, AppliedAt, Description)
|
||||
VALUES (@version, @appliedAt, @description);
|
||||
";
|
||||
cmd.Parameters.AddWithValue("@version", version);
|
||||
cmd.Parameters.AddWithValue("@appliedAt", DateTime.UtcNow.ToString("O"));
|
||||
cmd.Parameters.AddWithValue("@description", description);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene una connessione al database
|
||||
/// </summary>
|
||||
public async Task<SqliteConnection> GetConnectionAsync()
|
||||
{
|
||||
var connection = new SqliteConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
return connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una query SQL e ritorna il numero di righe affette
|
||||
/// </summary>
|
||||
public async Task<int> ExecuteNonQueryAsync(string sql, params SqliteParameter[] parameters)
|
||||
{
|
||||
await using var connection = await GetConnectionAsync();
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
if (parameters != null)
|
||||
{
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
}
|
||||
return await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue una query SQL e ritorna un valore scalare
|
||||
/// </summary>
|
||||
public async Task<object?> ExecuteScalarAsync(string sql, params SqliteParameter[] parameters)
|
||||
{
|
||||
await using var connection = await GetConnectionAsync();
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
if (parameters != null)
|
||||
{
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
}
|
||||
return await cmd.ExecuteScalarAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica la salute del database
|
||||
/// </summary>
|
||||
public async Task<bool> CheckDatabaseHealthAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = await GetConnectionAsync();
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table';";
|
||||
var result = await cmd.ExecuteScalarAsync();
|
||||
return Convert.ToInt32(result) > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene informazioni sul database
|
||||
/// </summary>
|
||||
public async Task<DatabaseInfo> GetDatabaseInfoAsync()
|
||||
{
|
||||
await using var connection = await GetConnectionAsync();
|
||||
|
||||
var info = new DatabaseInfo
|
||||
{
|
||||
Path = _databasePath,
|
||||
SizeBytes = new FileInfo(_databasePath).Length,
|
||||
Version = await GetDatabaseVersionAsync(connection)
|
||||
};
|
||||
|
||||
// Conta record per tabella
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM Auctions) as AuctionsCount,
|
||||
(SELECT COUNT(*) FROM BidHistory) as BidHistoryCount,
|
||||
(SELECT COUNT(*) FROM BidderStats) as BidderStatsCount,
|
||||
(SELECT COUNT(*) FROM AuctionLogs) as AuctionLogsCount,
|
||||
(SELECT COUNT(*) FROM ProductStats) as ProductStatsCount;
|
||||
";
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
if (await reader.ReadAsync())
|
||||
{
|
||||
info.AuctionsCount = reader.GetInt32(0);
|
||||
info.BidHistoryCount = reader.GetInt32(1);
|
||||
info.BidderStatsCount = reader.GetInt32(2);
|
||||
info.AuctionLogsCount = reader.GetInt32(3);
|
||||
info.ProductStatsCount = reader.GetInt32(4);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottimizza il database (VACUUM)
|
||||
/// </summary>
|
||||
public async Task OptimizeDatabaseAsync()
|
||||
{
|
||||
await using var connection = await GetConnectionAsync();
|
||||
await using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "VACUUM;";
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Backup del database
|
||||
/// </summary>
|
||||
public async Task<string> BackupDatabaseAsync()
|
||||
{
|
||||
var backupDir = Path.Combine(AppContext.BaseDirectory, "data", "backups");
|
||||
Directory.CreateDirectory(backupDir);
|
||||
|
||||
var backupPath = Path.Combine(backupDir, $"autobidder_backup_{DateTime.Now:yyyyMMdd_HHmmss}.db");
|
||||
|
||||
await using var source = await GetConnectionAsync();
|
||||
await using var destination = new SqliteConnection($"Data Source={backupPath}");
|
||||
await destination.OpenAsync();
|
||||
|
||||
source.BackupDatabase(destination);
|
||||
|
||||
return backupPath;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classe per rappresentare una migration
|
||||
/// </summary>
|
||||
private class Migration
|
||||
{
|
||||
public int Version { get; }
|
||||
public string Description { get; }
|
||||
private readonly Func<SqliteConnection, Task> _execute;
|
||||
|
||||
public Migration(int version, string description, Func<SqliteConnection, Task> execute)
|
||||
{
|
||||
Version = version;
|
||||
Description = description;
|
||||
_execute = execute;
|
||||
}
|
||||
|
||||
public async Task Execute(SqliteConnection connection)
|
||||
{
|
||||
await _execute(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informazioni sul database
|
||||
/// </summary>
|
||||
public class DatabaseInfo
|
||||
{
|
||||
public string Path { get; set; } = "";
|
||||
public long SizeBytes { get; set; }
|
||||
public int Version { get; set; }
|
||||
public int AuctionsCount { get; set; }
|
||||
public int BidHistoryCount { get; set; }
|
||||
public int BidderStatsCount { get; set; }
|
||||
public int AuctionLogsCount { get; set; }
|
||||
public int ProductStatsCount { get; set; }
|
||||
|
||||
public string SizeFormatted => FormatBytes(SizeBytes);
|
||||
|
||||
private static string FormatBytes(long bytes)
|
||||
{
|
||||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||||
double len = bytes;
|
||||
int order = 0;
|
||||
while (len >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
len = len / 1024;
|
||||
}
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,30 @@ namespace AutoBidder.Services
|
||||
public StatsService(StatisticsContext ctx)
|
||||
{
|
||||
_ctx = ctx;
|
||||
// Assicurati che il database esista (senza migrations)
|
||||
_ctx.Database.EnsureCreated();
|
||||
|
||||
// Assicurati che il database esista
|
||||
try
|
||||
{
|
||||
_ctx.Database.EnsureCreated();
|
||||
|
||||
// Verifica che la tabella ProductStats esista
|
||||
var canQuery = _ctx.ProductStats.Any();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[StatsService] Database initialization failed: {ex.Message}");
|
||||
// Prova a ricreare
|
||||
try
|
||||
{
|
||||
_ctx.Database.EnsureDeleted();
|
||||
_ctx.Database.EnsureCreated();
|
||||
Console.WriteLine("[StatsService] Database recreated successfully");
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Console.WriteLine($"[StatsService] Database recreation failed: {ex2.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeKey(string? productName, string? auctionUrl)
|
||||
@@ -101,9 +123,42 @@ namespace AutoBidder.Services
|
||||
// New: return all stats for export
|
||||
public async Task<List<ProductStat>> GetAllStatsAsync()
|
||||
{
|
||||
return await _ctx.ProductStats
|
||||
.OrderByDescending(p => p.LastSeen)
|
||||
.ToListAsync();
|
||||
try
|
||||
{
|
||||
// Verifica che la tabella esista prima di fare query
|
||||
if (!_ctx.Database.CanConnect())
|
||||
{
|
||||
Console.WriteLine("[StatsService] Database not available, returning empty list");
|
||||
return new List<ProductStat>();
|
||||
}
|
||||
|
||||
return await _ctx.ProductStats
|
||||
.OrderByDescending(p => p.LastSeen)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.SqliteErrorCode == 1)
|
||||
{
|
||||
// Table doesn't exist - return empty list
|
||||
Console.WriteLine($"[StatsService] Table doesn't exist: {ex.Message}");
|
||||
|
||||
// Try to create schema
|
||||
try
|
||||
{
|
||||
_ctx.Database.EnsureCreated();
|
||||
Console.WriteLine("[StatsService] Schema created, returning empty list");
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Console.WriteLine($"[StatsService] Failed to create schema: {ex2.Message}");
|
||||
}
|
||||
|
||||
return new List<ProductStat>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[StatsService] GetAllStatsAsync failed: {ex.Message}");
|
||||
return new List<ProductStat>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,5 @@
|
||||
Si è verificato un errore non gestito. Consultare la console del browser per ulteriori informazioni.
|
||||
</environment>
|
||||
<a href="" class="reload">Ricarica</a>
|
||||
<a class="dismiss">??</a>
|
||||
<a class="dismiss">?</a>
|
||||
</div>
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
<i class="bi bi-display me-2"></i> Monitor Aste
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-2 mb-2 animate-fade-in-left stagger-item">
|
||||
<NavLink class="nav-link hover-lift transition-all" href="browser">
|
||||
<i class="bi bi-globe me-2"></i> Browser
|
||||
</NavLink>
|
||||
</div>
|
||||
<div class="nav-item px-2 mb-2 animate-fade-in-left stagger-item">
|
||||
<NavLink class="nav-link hover-lift transition-all" href="freebids">
|
||||
<i class="bi bi-gift me-2"></i> Puntate Gratuite
|
||||
|
||||
@@ -1,127 +1,102 @@
|
||||
@inject AuctionMonitor AuctionMonitor
|
||||
@implements IDisposable
|
||||
|
||||
<div class="user-banner animate-fade-in">
|
||||
<div class="user-stats-banner">
|
||||
@if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
<div class="user-info animate-scale-in">
|
||||
<div class="user-avatar">
|
||||
<i class="bi bi-person-circle" style="font-size: 2rem;"></i>
|
||||
<div class="stats-row">
|
||||
<div class="stat-item stat-bids">
|
||||
<span class="stat-label">Puntate:</span>
|
||||
<span class="stat-value @GetBidsColorClass()">@remainingBids</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<div class="username">
|
||||
<i class="bi bi-person-fill me-1"></i>@username
|
||||
</div>
|
||||
<div class="remaining-bids mt-1">
|
||||
<span class="badge @GetBidsColorClass() badge-pulse">
|
||||
<i class="bi bi-hand-index-fill me-1"></i>@remainingBids puntate
|
||||
</span>
|
||||
</div>
|
||||
<div class="stat-separator">|</div>
|
||||
<div class="stat-item stat-credit">
|
||||
<span class="stat-label">Credito Shop:</span>
|
||||
<span class="stat-value text-success">EUR @shopCredit.ToString("F2")</span>
|
||||
</div>
|
||||
<div class="connection-status ms-3">
|
||||
<span class="badge bg-success animate-glow">
|
||||
<i class="bi bi-wifi"></i> Connesso
|
||||
</span>
|
||||
<div class="stat-separator">|</div>
|
||||
<div class="stat-item stat-wins">
|
||||
<span class="stat-label">Aste vinte da confermare:</span>
|
||||
<span class="stat-value text-warning">@auctionsWon</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="user-info animate-fade-in">
|
||||
<div class="user-avatar opacity-50">
|
||||
<i class="bi bi-person-x-fill" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<span class="text-light opacity-75">
|
||||
<i class="bi bi-x-circle me-1"></i>Non connesso
|
||||
<div class="stats-row">
|
||||
<div class="stat-item">
|
||||
<span class="text-muted">
|
||||
<i class="bi bi-x-circle me-2"></i>Non connesso
|
||||
</span>
|
||||
</div>
|
||||
<div class="connection-status ms-3">
|
||||
<a href="/settings" class="btn btn-sm btn-outline-light hover-lift">
|
||||
<i class="bi bi-box-arrow-in-right"></i> Connetti
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.user-banner {
|
||||
padding: 1rem;
|
||||
background: linear-gradient(135deg, rgba(13, 110, 253, 0.1), rgba(13, 202, 240, 0.1));
|
||||
border-radius: 10px;
|
||||
margin: 0.5rem;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
.user-stats-banner {
|
||||
background: #1e1e1e;
|
||||
border-bottom: 1px solid #3e3e42;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
.stats-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.user-avatar:hover {
|
||||
transform: scale(1.1) rotate(5deg);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
font-size: 1.1rem;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.remaining-bids .badge {
|
||||
font-size: 0.9rem;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
.stat-label {
|
||||
color: #8b949e;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.connection-status .badge {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
font-weight: 500;
|
||||
.stat-value {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.badge-low-bids {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
.stat-separator {
|
||||
color: #3e3e42;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.badge-medium-bids {
|
||||
background: linear-gradient(135deg, #ffc107, #ff9800);
|
||||
.bids-low {
|
||||
color: #f85149 !important;
|
||||
}
|
||||
|
||||
.badge-high-bids {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
.bids-medium {
|
||||
color: #d29922 !important;
|
||||
}
|
||||
|
||||
.bids-high {
|
||||
color: #3fb950 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private string? username;
|
||||
private int remainingBids;
|
||||
private double shopCredit;
|
||||
private int auctionsWon;
|
||||
private System.Threading.Timer? updateTimer;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_ = UpdateUserInfo(); // Fire-and-forget è intenzionale qui
|
||||
_ = UpdateUserInfo();
|
||||
updateTimer = new System.Threading.Timer(async _ =>
|
||||
{
|
||||
await UpdateUserInfo();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
||||
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
private async Task UpdateUserInfo()
|
||||
@@ -129,13 +104,15 @@
|
||||
var session = AuctionMonitor.GetSession();
|
||||
username = session?.Username;
|
||||
remainingBids = session?.RemainingBids ?? 0;
|
||||
shopCredit = session?.ShopCredit ?? 0;
|
||||
auctionsWon = 0; // TODO: aggiungere campo al model BidooSession
|
||||
}
|
||||
|
||||
private string GetBidsColorClass()
|
||||
{
|
||||
if (remainingBids < 50) return "badge-low-bids";
|
||||
if (remainingBids < 150) return "badge-medium-bids";
|
||||
return "badge-high-bids";
|
||||
if (remainingBids < 50) return "bids-low";
|
||||
if (remainingBids < 150) return "bids-medium";
|
||||
return "bids-high";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -253,11 +253,11 @@ namespace AutoBidder.Utilities
|
||||
{
|
||||
summary += $" | Valore: {value.BuyNowPrice.Value:F2}€";
|
||||
|
||||
if (value.Savings.HasValue)
|
||||
if (value.Savings.HasValue && value.SavingsPercentage.HasValue)
|
||||
{
|
||||
if (value.Savings.Value > 0)
|
||||
{
|
||||
summary += $" | Risparmio: {value.Savings.Value:F2}€ ({value.SavingsPercentage:F1}%)";
|
||||
summary += $" | Risparmio: {value.Savings.Value:F2}€ ({value.SavingsPercentage.Value:F1}%)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -284,13 +284,17 @@ namespace AutoBidder.Utilities
|
||||
return $"?? Costo totale: {value.TotalCostIfWin:F2}€ (prezzo: {value.CurrentPrice:F2}€ + puntate: {value.MyBidsCost:F2}€)";
|
||||
}
|
||||
|
||||
if (value.IsWorthIt)
|
||||
if (value.IsWorthIt && value.Savings.HasValue && value.SavingsPercentage.HasValue)
|
||||
{
|
||||
return $"? Conveniente! Risparmio: {value.Savings:F2}€ ({value.SavingsPercentage:F1}%) - Totale: {value.TotalCostIfWin:F2}€ vs {value.BuyNowPrice.Value:F2}€";
|
||||
return $"? Conveniente! Risparmio: {value.Savings.Value:F2}€ ({value.SavingsPercentage.Value:F1}%) - Totale: {value.TotalCostIfWin:F2}€ vs {value.BuyNowPrice.Value:F2}€";
|
||||
}
|
||||
else if (value.Savings.HasValue && value.SavingsPercentage.HasValue)
|
||||
{
|
||||
return $"? Non conveniente! Spesa extra: {Math.Abs(value.Savings.Value):F2}€ ({Math.Abs(value.SavingsPercentage.Value):F1}%) - Totale: {value.TotalCostIfWin:F2}€ vs {value.BuyNowPrice.Value:F2}€";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"? Non conveniente! Spesa extra: {Math.Abs(value.Savings.Value):F2}€ ({Math.Abs(value.SavingsPercentage.Value):F1}%) - Totale: {value.TotalCostIfWin:F2}€ vs {value.BuyNowPrice.Value:F2}€";
|
||||
return $"?? Costo totale: {value.TotalCostIfWin:F2}€";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace AutoBidder.Utilities
|
||||
public double DefaultMinPrice { get; set; } = 0;
|
||||
public double DefaultMaxPrice { get; set; } = 0;
|
||||
public int DefaultMaxClicks { get; set; } = 0;
|
||||
public int DefaultMinResets { get; set; } = 0;
|
||||
public int DefaultMaxResets { get; set; } = 0;
|
||||
|
||||
// LIMITI LOG
|
||||
/// <summary>
|
||||
|
||||
49
Mimante/docker-compose.dev.yml
Normal file
49
Mimante/docker-compose.dev.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
version: '3.8'
|
||||
|
||||
# Docker Compose per ambiente di sviluppo
|
||||
# Usa: docker-compose -f docker-compose.dev.yml up
|
||||
|
||||
services:
|
||||
autobidder-dev:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: build # Ferma alla fase build per debugging
|
||||
container_name: autobidder-dev
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "5001:5001"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./wwwroot:/app/wwwroot # Hot-reload static files
|
||||
- ./logs:/app/logs
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_URLS=http://+:5000;https://+:5001
|
||||
- Logging__LogLevel__Default=Debug
|
||||
- Logging__LogLevel__Microsoft=Information
|
||||
restart: no # No auto-restart in dev
|
||||
networks:
|
||||
- dev-network
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
# SQLite browser per debug database
|
||||
sqlite-web:
|
||||
image: coleifer/sqlite-web
|
||||
container_name: sqlite-web
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- SQLITE_DATABASE=/data/autobidder.db
|
||||
command: ["sqlite_web", "-H", "0.0.0.0", "/data/autobidder.db"]
|
||||
networks:
|
||||
- dev-network
|
||||
profiles:
|
||||
- debug # Avvia con: docker-compose --profile debug up
|
||||
|
||||
networks:
|
||||
dev-network:
|
||||
driver: bridge
|
||||
@@ -8,9 +8,29 @@ services:
|
||||
container_name: autobidder
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "5001:5001"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- autobidder-keys:/root/.aspnet/DataProtection-Keys
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_URLS=http://+:5000
|
||||
- ASPNETCORE_URLS=http://+:5000;https://+:5001
|
||||
- ASPNETCORE_Kestrel__Certificates__Default__Path=/app/cert/autobidder.pfx
|
||||
- ASPNETCORE_Kestrel__Certificates__Default__Password=${CERT_PASSWORD:-AutoBidder2024}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
networks:
|
||||
- autobidder-network
|
||||
|
||||
volumes:
|
||||
autobidder-keys:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
autobidder-network:
|
||||
driver: bridge
|
||||
|
||||
559
Mimante/wwwroot/css/app-wpf.css
Normal file
559
Mimante/wwwroot/css/app-wpf.css
Normal file
@@ -0,0 +1,559 @@
|
||||
/* app-wpf.css - WPF Dark Theme + Modern Sidebar */
|
||||
|
||||
:root {
|
||||
/* WPF Dark Theme Palette */
|
||||
--bg-primary: #1e1e1e;
|
||||
--bg-secondary: #252526;
|
||||
--bg-tertiary: #2d2d30;
|
||||
--bg-hover: #3e3e42;
|
||||
--bg-selected: #094771;
|
||||
--border-color: #3e3e42;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #cccccc;
|
||||
--text-muted: #808080;
|
||||
|
||||
/* WPF Accent Colors */
|
||||
--primary-color: #007acc;
|
||||
--success-color: #00d800;
|
||||
--warning-color: #ffb700;
|
||||
--danger-color: #e81123;
|
||||
--info-color: #00b7c3;
|
||||
|
||||
/* Log Syntax Colors */
|
||||
--log-success: #00d800;
|
||||
--log-warning: #ffb700;
|
||||
--log-error: #f48771;
|
||||
--log-info: #4ec9b0;
|
||||
--log-debug: #569cd6;
|
||||
--log-timestamp: #808080;
|
||||
}
|
||||
|
||||
/* === GLOBAL === */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* === LAYOUT === */
|
||||
.page {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Sidebar Moderna - 250px come prima */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: linear-gradient(180deg, #1c2128 0%, #161b22 50%, #0d1117 100%);
|
||||
border-right: 1px solid var(--border-color);
|
||||
z-index: 1000;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 250px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* === AUCTION MONITOR === */
|
||||
.auction-monitor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar .btn {
|
||||
padding: 0.4rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--success-color) !important;
|
||||
border-color: var(--success-color) !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: var(--warning-color) !important;
|
||||
border-color: var(--warning-color) !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger-color) !important;
|
||||
border-color: var(--danger-color) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary-color) !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: var(--info-color) !important;
|
||||
border-color: var(--info-color) !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-hover) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
/* === GRID LAYOUT CORRETTO === */
|
||||
/* Auctions List: sinistra, full height */
|
||||
/* Global Log: alto destra */
|
||||
/* Auction Details: basso destra */
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
grid-template-rows: 1fr 300px;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.auctions-list {
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* LOG GLOBALE - ALTO DESTRA */
|
||||
.global-log {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* DETTAGLI ASTA - BASSO DESTRA */
|
||||
.auction-details {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.auctions-list h3,
|
||||
.global-log h4,
|
||||
.auction-details h3 {
|
||||
color: var(--success-color);
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
padding: 0.5rem;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* === TABLES === */
|
||||
.table {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.813rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table thead {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.4rem 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table tbody tr {
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.table tbody tr.table-active {
|
||||
background: var(--bg-selected) !important;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.table tbody td {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.3rem 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* === LOG === */
|
||||
.log-box {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem;
|
||||
background: var(--bg-primary);
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.log-box div {
|
||||
padding: 0.1rem 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.log-success {
|
||||
color: var(--log-success);
|
||||
}
|
||||
|
||||
.log-warning {
|
||||
color: var(--log-warning);
|
||||
}
|
||||
|
||||
.log-error {
|
||||
color: var(--log-error);
|
||||
}
|
||||
|
||||
.log-info {
|
||||
color: var(--log-info);
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: var(--log-timestamp);
|
||||
}
|
||||
|
||||
/* === FORMS === */
|
||||
.form-control, .form-select {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.813rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--text-primary);
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 122, 204, 0.25);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 0.813rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.form-check-input:checked {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* === BADGES === */
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge.bg-success {
|
||||
background: var(--success-color) !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge.bg-warning {
|
||||
background: var(--warning-color) !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge.bg-danger {
|
||||
background: var(--danger-color) !important;
|
||||
}
|
||||
|
||||
.badge.bg-info {
|
||||
background: var(--info-color) !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.badge.bg-secondary {
|
||||
background: var(--bg-hover) !important;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.badge.bg-primary {
|
||||
background: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
/* === BUTTONS GROUP === */
|
||||
.btn-group-sm .btn {
|
||||
padding: 0.2rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* === AUCTION DETAILS SECTIONS === */
|
||||
.auction-info {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 0.75rem;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.info-group {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.info-group label {
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.813rem;
|
||||
}
|
||||
|
||||
.auction-log, .bidders-stats {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.auction-log h4, .bidders-stats h4 {
|
||||
color: var(--success-color);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* === INPUT GROUPS === */
|
||||
.input-group .form-control {
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.813rem;
|
||||
}
|
||||
|
||||
/* === MODAL === */
|
||||
.modal-content {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: var(--bg-tertiary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
filter: invert(1) grayscale(100%) brightness(200%);
|
||||
}
|
||||
|
||||
/* === ALERTS === */
|
||||
.alert {
|
||||
border-radius: 3px;
|
||||
border: 1px solid;
|
||||
font-size: 0.813rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: rgba(0, 183, 195, 0.1);
|
||||
border-color: var(--info-color);
|
||||
color: var(--info-color);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: rgba(0, 216, 0, 0.1);
|
||||
border-color: var(--success-color);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: rgba(255, 183, 0, 0.1);
|
||||
border-color: var(--warning-color);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: rgba(232, 17, 35, 0.1);
|
||||
border-color: var(--danger-color);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
.alert-secondary {
|
||||
background: rgba(62, 62, 66, 0.1);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* === CARDS === */
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
/* === SCROLLBAR === */
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-hover);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* === TEXT COLORS === */
|
||||
.text-success {
|
||||
color: var(--success-color) !important;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: var(--warning-color) !important;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: var(--danger-color) !important;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: var(--info-color) !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.fw-semibold {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* === RESPONSIVE === */
|
||||
@media (max-width: 1200px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto auto auto;
|
||||
}
|
||||
|
||||
.auctions-list {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.global-log {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.auction-details {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user