Add multi-mode HTML, Docker, Helm chart, and deploy script
All checks were successful
Build and Deploy / build (push) Successful in 46s
All checks were successful
Build and Deploy / build (push) Successful in 46s
- Add shop-mode.html and project-mode.html for separate calculation modes - Refactor index.html as a landing page for mode selection - Add Dockerfile with optimized nginx config and healthcheck - Add .dockerignore for cleaner Docker builds - Add deploy.sh for Helm/Kubernetes deployment automation - Add helm-chart/ with values.yaml, Chart.yaml, templates, and documentation - Update README.md with full instructions, features, and CI/CD examples
This commit is contained in:
38
.dockerignore
Normal file
38
.dockerignore
Normal file
@@ -0,0 +1,38 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitea
|
||||
|
||||
# Helm
|
||||
helm-chart/
|
||||
*.md
|
||||
|
||||
# CI/CD
|
||||
.gitlab-ci.yml
|
||||
.github/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
LICENSE
|
||||
docs/
|
||||
|
||||
# Node modules (if any)
|
||||
node_modules/
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
65
Dockerfile
65
Dockerfile
@@ -1,7 +1,70 @@
|
||||
# Multi-stage build for optimized nginx image
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
# Remove default nginx static assets
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy static files
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
COPY project-mode.html /usr/share/nginx/html/
|
||||
COPY shop-mode.html /usr/share/nginx/html/
|
||||
|
||||
# Create a custom nginx configuration for better caching and security
|
||||
RUN cat > /etc/nginx/conf.d/default.conf <<'EOF'
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Main location
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Run nginx as non-root user
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
touch /var/run/nginx.pid && \
|
||||
chown -R nginx:nginx /var/run/nginx.pid
|
||||
|
||||
USER nginx
|
||||
|
||||
# Expose port 80
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget --quiet --tries=1 --spider http://localhost/health || exit 1
|
||||
|
||||
# Start nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
345
README.md
345
README.md
@@ -1,77 +1,308 @@
|
||||
# Calcolatore Prezzi Software
|
||||
|
||||
Un'applicazione web per calcolare i prezzi di progetti software in base a diversi parametri e metodologie di stima.
|
||||
Sistema professionale per la creazione di preventivi software con due modalità operative:
|
||||
|
||||
- **Menu Principale** (`index.html`): Pagina di selezione della modalità con interfaccia intuitiva
|
||||
- **Modalità Progetto** (`project-mode.html`): Calcolo basato su ore, persone, milestone e regime fiscale italiano
|
||||
- **Modalità Negozio** (`shop-mode.html`): Calcolo basato su articoli, quantità e listino prezzi (come un e-commerce)
|
||||
|
||||
## Caratteristiche
|
||||
|
||||
- Interfaccia utente moderna e responsive
|
||||
- Calcolo automatico dei prezzi basato su parametri configurabili
|
||||
- Supporto per diverse metodologie di stima
|
||||
- Design ottimizzato per il mercato italiano
|
||||
### Modalità Standard
|
||||
|
||||
## Installazione e Utilizzo
|
||||
- ✅ Gestione team con tariffe personalizzate per membro
|
||||
- ✅ Calcolo milestone con assegnazione task a membri specifici
|
||||
- ✅ Supporto per diversi regimi fiscali italiani (Forfettario, Ordinario, SRL, SRLS, ecc.)
|
||||
- ✅ Calcolo automatico di IRPEF, IRES, IRAP, INPS
|
||||
- ✅ Generazione PDF per cliente e documento interno
|
||||
- ✅ Salvataggio e caricamento preventivi
|
||||
- ✅ Interfaccia responsive con Alpine.js e Tailwind CSS
|
||||
|
||||
### Utilizzo con Docker
|
||||
### Modalità Negozio
|
||||
|
||||
1. **Costruire l'immagine Docker:**
|
||||
```bash
|
||||
docker build -t git.commandware.com/dnviti/calcolatore_prezzi_software .
|
||||
```
|
||||
|
||||
2. **Eseguire il container:**
|
||||
```bash
|
||||
docker run -p 8080:80 git.commandware.com/dnviti/calcolatore_prezzi_software
|
||||
```
|
||||
|
||||
3. **Oppure utilizzare l'immagine dal registry:**
|
||||
```bash
|
||||
docker pull git.commandware.com/dnviti/calcolatore_prezzi_software:main
|
||||
docker run -p 8080:80 git.commandware.com/dnviti/calcolatore_prezzi_software:main
|
||||
```
|
||||
|
||||
3. **Accedere all'applicazione:**
|
||||
Aprire il browser e navigare a `http://localhost:8080`
|
||||
|
||||
### Sviluppo Locale
|
||||
|
||||
Per sviluppo locale, aprire semplicemente il file `index.html` in un browser web.
|
||||
|
||||
## Deployment
|
||||
|
||||
Il progetto include una pipeline CI/CD per Gitea che automaticamente:
|
||||
|
||||
- Costruisce l'immagine Docker
|
||||
- Pubblica sul registry Gitea (git.commandware.com)
|
||||
- Si attiva su push al branch `main` o su pull request
|
||||
|
||||
### Configurazione Secrets
|
||||
|
||||
Per utilizzare la pipeline, configurare i seguenti secrets nel repository Gitea:
|
||||
|
||||
- `GITEA_USERNAME`: Username Gitea
|
||||
- `GITEA_TOKEN`: Token di accesso Gitea con permessi di scrittura al registry
|
||||
- ✅ Gestione catalogo articoli con codici SKU
|
||||
- ✅ Calcolo prezzi unitari e quantità
|
||||
- ✅ Sconti per articolo
|
||||
- ✅ Gestione spese di spedizione
|
||||
- ✅ Calcolo IVA configurabile
|
||||
- ✅ Generazione PDF preventivo
|
||||
- ✅ Interfaccia intuitiva per vendita prodotti/servizi
|
||||
|
||||
## Struttura del Progetto
|
||||
|
||||
```
|
||||
.
|
||||
├── index.html # Applicazione web principale
|
||||
├── Dockerfile # Configurazione Docker
|
||||
├── .gitea/
|
||||
│ └── workflows/
|
||||
│ └── build.yml # Pipeline CI/CD
|
||||
└── README.md # Documentazione
|
||||
├── index.html # Menu principale per selezione modalità
|
||||
├── project-mode.html # Modalità progetto (ore/persone/milestone)
|
||||
├── shop-mode.html # Modalità negozio (articoli/quantità)
|
||||
├── Dockerfile # Build immagine Docker
|
||||
├── deploy.sh # Script deploy automatico
|
||||
├── .dockerignore # Ignore file per Docker
|
||||
└── helm-chart/ # Helm chart per Kubernetes
|
||||
├── Chart.yaml # Definizione chart
|
||||
├── values.yaml # Valori di configurazione
|
||||
├── README.md # Documentazione Helm
|
||||
├── .helmignore # Ignore file per Helm
|
||||
└── templates/ # Template Kubernetes
|
||||
├── _helpers.tpl # Helper functions
|
||||
├── configmap.yaml # ConfigMap per HTML
|
||||
└── NOTES.txt # Note post-install
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Utilizzo Locale
|
||||
|
||||
1. **Apri direttamente nel browser:**
|
||||
|
||||
```bash
|
||||
# Modalità standard
|
||||
firefox index.html
|
||||
|
||||
# Modalità negozio
|
||||
firefox shop-mode.html
|
||||
```
|
||||
|
||||
2. **Oppure con un server web locale:**
|
||||
|
||||
```bash
|
||||
# Python 3
|
||||
python3 -m http.server 8000
|
||||
|
||||
# PHP
|
||||
php -S localhost:8000
|
||||
|
||||
# Node.js (con http-server)
|
||||
npx http-server -p 8000
|
||||
```
|
||||
|
||||
Poi visita: `http://localhost:8000`
|
||||
|
||||
### Deploy con Docker
|
||||
|
||||
1. **Build dell'immagine:**
|
||||
|
||||
```bash
|
||||
docker build -t calcolatore-prezzi:latest .
|
||||
```
|
||||
|
||||
2. **Run del container:**
|
||||
|
||||
```bash
|
||||
docker run -d -p 8080:80 --name calcolatore calcolatore-prezzi:latest
|
||||
```
|
||||
|
||||
3. **Accesso:**
|
||||
```
|
||||
http://localhost:8080/ # Menu principale
|
||||
http://localhost:8080/project-mode.html # Modalità progetto
|
||||
http://localhost:8080/shop-mode.html # Modalità negozio
|
||||
```
|
||||
|
||||
### Deploy su Kubernetes con Helm
|
||||
|
||||
#### Prerequisiti
|
||||
|
||||
- Kubernetes cluster (1.19+)
|
||||
- Helm 3.0+
|
||||
- kubectl configurato
|
||||
|
||||
#### Deploy Rapido
|
||||
|
||||
1. **Aggiorna le dipendenze Helm:**
|
||||
|
||||
```bash
|
||||
cd helm-chart
|
||||
helm dependency update
|
||||
cd ..
|
||||
```
|
||||
|
||||
2. **Deploy con lo script automatico:**
|
||||
|
||||
```bash
|
||||
# Deploy base
|
||||
./deploy.sh
|
||||
|
||||
# Deploy in namespace specifico
|
||||
./deploy.sh -n production
|
||||
|
||||
# Deploy con valori personalizzati
|
||||
./deploy.sh -f values-prod.yaml -n production -t v1.0.0
|
||||
|
||||
# Upgrade deployment esistente
|
||||
./deploy.sh -u -n production -t v1.0.1
|
||||
```
|
||||
|
||||
3. **Deploy manuale con Helm:**
|
||||
```bash
|
||||
helm install calcolatore-prezzi ./helm-chart \
|
||||
--namespace production \
|
||||
--create-namespace \
|
||||
--set-file configMaps.html-content.data.index\.html=./index.html \
|
||||
--set-file configMaps.html-content.data.project-mode\.html=./project-mode.html \
|
||||
--set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
```
|
||||
|
||||
#### Configurazione Ingress
|
||||
|
||||
Modifica `helm-chart/values.yaml`:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
hosts:
|
||||
- host: calcolatore.tuodominio.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: calcolatore-tls
|
||||
hosts:
|
||||
- calcolatore.tuodominio.com
|
||||
```
|
||||
|
||||
#### Verifica Deploy
|
||||
|
||||
```bash
|
||||
# Stato dei pod
|
||||
kubectl get pods -n production
|
||||
|
||||
# Logs
|
||||
kubectl logs -n production -l app.kubernetes.io/name=calcolatore-prezzi -f
|
||||
|
||||
# Port-forward per test locale
|
||||
kubectl port-forward -n production svc/calcolatore-prezzi 8080:80
|
||||
```
|
||||
|
||||
## Helm Chart
|
||||
|
||||
Il chart utilizza [base-helm](https://git.commandware.com/GitOps/base-helm.git) come dipendenza comune per standardizzare le risorse Kubernetes.
|
||||
|
||||
### Parametri Principali
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
| ------------------------- | ------------------- | ----------- |
|
||||
| `replicaCount` | Numero di repliche | `2` |
|
||||
| `image.repository` | Repository immagine | `nginx` |
|
||||
| `image.tag` | Tag immagine | `alpine` |
|
||||
| `service.type` | Tipo service | `ClusterIP` |
|
||||
| `service.port` | Porta service | `80` |
|
||||
| `ingress.enabled` | Abilita ingress | `true` |
|
||||
| `resources.limits.cpu` | Limite CPU | `200m` |
|
||||
| `resources.limits.memory` | Limite memoria | `256Mi` |
|
||||
| `autoscaling.enabled` | Abilita HPA | `false` |
|
||||
|
||||
Vedi `helm-chart/README.md` per la documentazione completa.
|
||||
|
||||
## CI/CD Examples
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t registry.example.com/calcolatore:${{ github.sha }} .
|
||||
|
||||
- name: Push image
|
||||
run: docker push registry.example.com/calcolatore:${{ github.sha }}
|
||||
|
||||
- name: Deploy to Kubernetes
|
||||
run: |
|
||||
helm dependency update ./helm-chart
|
||||
./deploy.sh -u -n production -t ${{ github.sha }}
|
||||
```
|
||||
|
||||
### GitLab CI
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- build
|
||||
- deploy
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
|
||||
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- helm dependency update ./helm-chart
|
||||
- ./deploy.sh -u -n production -t $CI_COMMIT_SHA
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
## Tecnologie Utilizzate
|
||||
|
||||
- HTML5
|
||||
- CSS3 (Tailwind CSS)
|
||||
- JavaScript
|
||||
- Nginx (per serving statico)
|
||||
- Docker
|
||||
- Gitea Actions
|
||||
- **Frontend:** HTML5, CSS3, JavaScript (Vanilla + Alpine.js)
|
||||
- **Styling:** Tailwind CSS (via CDN)
|
||||
- **PDF Generation:** jsPDF
|
||||
- **Icons:** Font Awesome
|
||||
- **Containerization:** Docker
|
||||
- **Orchestration:** Kubernetes + Helm 3
|
||||
- **Web Server:** Nginx Alpine
|
||||
|
||||
## Funzionalità Aggiuntive
|
||||
|
||||
### Modalità Standard
|
||||
|
||||
- Calcolo dettagliato per regime fiscale italiano
|
||||
- Supporto INPS, rivalsa, ritenuta d'acconto
|
||||
- Gestione team con tariffe differenziate
|
||||
- Milestone con task assegnabili a membri specifici
|
||||
- Due PDF separati: cliente (pubblico) e interno (riservato)
|
||||
|
||||
### Modalità Negozio
|
||||
|
||||
- Gestione inventario articoli
|
||||
- Codici SKU personalizzabili
|
||||
- Sconti per singolo articolo
|
||||
- Calcolo spese di spedizione
|
||||
- IVA configurabile (0%, 4%, 10%, 22%)
|
||||
|
||||
## Browser Supportati
|
||||
|
||||
- Chrome/Chromium 90+
|
||||
- Firefox 88+
|
||||
- Safari 14+
|
||||
- Edge 90+
|
||||
|
||||
## Licenza
|
||||
|
||||
[Inserire informazioni sulla licenza]
|
||||
[Inserisci qui la tua licenza]
|
||||
|
||||
## Autore
|
||||
|
||||
[Il tuo nome]
|
||||
|
||||
## Supporto
|
||||
|
||||
Per problemi o domande, apri una issue nel repository.
|
||||
|
||||
## Contributing
|
||||
|
||||
Le pull request sono benvenute. Per modifiche importanti, apri prima una issue per discutere cosa vorresti cambiare.
|
||||
|
||||
---
|
||||
|
||||
**Note:**
|
||||
|
||||
- Questo progetto è pensato per il mercato italiano e include calcoli fiscali specifici
|
||||
- I calcoli fiscali sono indicativi e dovresti sempre consultare un commercialista
|
||||
- Il chart Helm usa base-helm come standard per deployment Kubernetes
|
||||
|
||||
234
deploy.sh
Executable file
234
deploy.sh
Executable file
@@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Deploy script for Calcolatore Prezzi Software
|
||||
# This script helps deploy the application to Kubernetes using Helm
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
CHART_PATH="./helm-chart"
|
||||
RELEASE_NAME="calcolatore-prezzi"
|
||||
NAMESPACE="default"
|
||||
VALUES_FILE=""
|
||||
IMAGE_TAG="latest"
|
||||
|
||||
# Functions
|
||||
print_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
Deploy Calcolatore Prezzi Software to Kubernetes using Helm
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-n, --namespace NAME Kubernetes namespace (default: default)
|
||||
-r, --release NAME Helm release name (default: calcolatore-prezzi)
|
||||
-f, --values FILE Values file for Helm
|
||||
-t, --tag TAG Docker image tag (default: latest)
|
||||
-u, --upgrade Upgrade existing release
|
||||
-d, --dry-run Perform a dry run
|
||||
--uninstall Uninstall the release
|
||||
--build Build Docker image before deploy
|
||||
|
||||
EXAMPLES:
|
||||
# Basic deployment
|
||||
$0
|
||||
|
||||
# Deploy with custom values
|
||||
$0 -f values-production.yaml -n production
|
||||
|
||||
# Upgrade existing deployment
|
||||
$0 -u -t v1.2.3 -n production
|
||||
|
||||
# Dry run
|
||||
$0 -d -f values-production.yaml
|
||||
|
||||
# Build and deploy
|
||||
$0 --build -t v1.0.0
|
||||
|
||||
# Uninstall
|
||||
$0 --uninstall -n production
|
||||
EOF
|
||||
}
|
||||
|
||||
check_requirements() {
|
||||
print_info "Checking requirements..."
|
||||
|
||||
if ! command -v helm &> /dev/null; then
|
||||
print_error "Helm is not installed. Please install Helm 3.0+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
print_error "kubectl is not installed. Please install kubectl"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "All requirements satisfied"
|
||||
}
|
||||
|
||||
update_dependencies() {
|
||||
print_info "Updating Helm dependencies..."
|
||||
cd "$CHART_PATH"
|
||||
helm dependency update
|
||||
cd - > /dev/null
|
||||
print_info "Dependencies updated"
|
||||
}
|
||||
|
||||
build_image() {
|
||||
print_info "Building Docker image..."
|
||||
docker build -t "calcolatore-prezzi:${IMAGE_TAG}" .
|
||||
print_info "Docker image built successfully: calcolatore-prezzi:${IMAGE_TAG}"
|
||||
}
|
||||
|
||||
deploy() {
|
||||
local action="install"
|
||||
local dry_run=""
|
||||
|
||||
if [ "$UPGRADE" = true ]; then
|
||||
action="upgrade"
|
||||
fi
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
dry_run="--dry-run --debug"
|
||||
fi
|
||||
|
||||
print_info "Preparing to ${action} release '${RELEASE_NAME}' in namespace '${NAMESPACE}'..."
|
||||
|
||||
# Build helm command
|
||||
local helm_cmd="helm ${action} ${RELEASE_NAME} ${CHART_PATH}"
|
||||
helm_cmd="${helm_cmd} --namespace ${NAMESPACE}"
|
||||
helm_cmd="${helm_cmd} --create-namespace"
|
||||
|
||||
if [ -n "$VALUES_FILE" ]; then
|
||||
helm_cmd="${helm_cmd} -f ${VALUES_FILE}"
|
||||
fi
|
||||
|
||||
helm_cmd="${helm_cmd} --set image.tag=${IMAGE_TAG}"
|
||||
|
||||
# Inject HTML files
|
||||
helm_cmd="${helm_cmd} --set-file configMaps.html-content.data.index\\.html=./index.html"
|
||||
helm_cmd="${helm_cmd} --set-file configMaps.html-content.data.project-mode\\.html=./project-mode.html"
|
||||
helm_cmd="${helm_cmd} --set-file configMaps.html-content.data.shop-mode\\.html=./shop-mode.html"
|
||||
|
||||
if [ -n "$dry_run" ]; then
|
||||
helm_cmd="${helm_cmd} ${dry_run}"
|
||||
fi
|
||||
|
||||
print_info "Executing: ${helm_cmd}"
|
||||
eval "$helm_cmd"
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
print_info "Deployment successful!"
|
||||
print_info "Checking deployment status..."
|
||||
kubectl rollout status deployment/${RELEASE_NAME} -n ${NAMESPACE} --timeout=5m || true
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
print_warn "Uninstalling release '${RELEASE_NAME}' from namespace '${NAMESPACE}'..."
|
||||
helm uninstall ${RELEASE_NAME} -n ${NAMESPACE}
|
||||
print_info "Release uninstalled successfully"
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
UPGRADE=false
|
||||
DRY_RUN=false
|
||||
UNINSTALL=false
|
||||
BUILD=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-n|--namespace)
|
||||
NAMESPACE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-r|--release)
|
||||
RELEASE_NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
-f|--values)
|
||||
VALUES_FILE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--tag)
|
||||
IMAGE_TAG="$2"
|
||||
shift 2
|
||||
;;
|
||||
-u|--upgrade)
|
||||
UPGRADE=true
|
||||
shift
|
||||
;;
|
||||
-d|--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--uninstall)
|
||||
UNINSTALL=true
|
||||
shift
|
||||
;;
|
||||
--build)
|
||||
BUILD=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Main execution
|
||||
print_info "===== Calcolatore Prezzi Software - Deploy Script ====="
|
||||
|
||||
check_requirements
|
||||
|
||||
if [ "$UNINSTALL" = true ]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$BUILD" = true ]; then
|
||||
build_image
|
||||
fi
|
||||
|
||||
update_dependencies
|
||||
deploy
|
||||
|
||||
print_info "===== Deployment Complete ====="
|
||||
|
||||
if [ "$DRY_RUN" != true ]; then
|
||||
print_info ""
|
||||
print_info "To check the status:"
|
||||
print_info " kubectl get pods -n ${NAMESPACE} -l app.kubernetes.io/name=${RELEASE_NAME}"
|
||||
print_info ""
|
||||
print_info "To view logs:"
|
||||
print_info " kubectl logs -n ${NAMESPACE} -l app.kubernetes.io/name=${RELEASE_NAME} -f"
|
||||
print_info ""
|
||||
print_info "To access the application:"
|
||||
print_info " kubectl port-forward -n ${NAMESPACE} svc/${RELEASE_NAME} 8080:80"
|
||||
print_info " Then visit: http://localhost:8080"
|
||||
fi
|
||||
26
helm-chart/.helmignore
Normal file
26
helm-chart/.helmignore
Normal file
@@ -0,0 +1,26 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
# Custom
|
||||
README.md
|
||||
.helmignore
|
||||
26
helm-chart/Chart.yaml
Normal file
26
helm-chart/Chart.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
apiVersion: v2
|
||||
name: calcolatore-prezzi-software
|
||||
description: A Helm chart for Calcolatore Prezzi Software application
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "1.0.0"
|
||||
|
||||
# Dependency on base-helm common chart
|
||||
dependencies:
|
||||
- name: base-helm
|
||||
version: "*"
|
||||
repository: "https://git.commandware.com/GitOps/base-helm.git"
|
||||
alias: common
|
||||
|
||||
maintainers:
|
||||
- name: Your Name
|
||||
email: your-email@example.com
|
||||
|
||||
keywords:
|
||||
- calculator
|
||||
- pricing
|
||||
- software
|
||||
- static-website
|
||||
|
||||
sources:
|
||||
- https://github.com/your-repo/calcolatore-prezzi-software
|
||||
300
helm-chart/README.md
Normal file
300
helm-chart/README.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# Calcolatore Prezzi Software - Helm Chart
|
||||
|
||||
Questo è un Helm chart semplificato per il deploy del Calcolatore Prezzi Software che utilizza il [base-helm](https://git.commandware.com/GitOps/base-helm.git) come chart comune.
|
||||
|
||||
## Prerequisiti
|
||||
|
||||
- Kubernetes 1.19+
|
||||
- Helm 3.0+
|
||||
- Nginx Ingress Controller (opzionale, per l'ingress)
|
||||
- Cert-manager (opzionale, per i certificati SSL)
|
||||
|
||||
## Installazione
|
||||
|
||||
### 1. Aggiornare le dipendenze
|
||||
|
||||
Prima di installare il chart, è necessario aggiornare le dipendenze per scaricare il base-helm:
|
||||
|
||||
```bash
|
||||
cd helm-chart
|
||||
helm dependency update
|
||||
```
|
||||
|
||||
### 2. Installazione base
|
||||
|
||||
```bash
|
||||
helm install calcolatore-prezzi ./helm-chart
|
||||
```
|
||||
|
||||
### 3. Installazione con valori personalizzati
|
||||
|
||||
```bash
|
||||
helm install calcolatore-prezzi ./helm-chart -f custom-values.yaml
|
||||
```
|
||||
|
||||
### 4. Installazione con file HTML personalizzati
|
||||
|
||||
Per iniettare i tuoi file HTML durante l'installazione:
|
||||
|
||||
```bash
|
||||
helm install calcolatore-prezzi ./helm-chart \
|
||||
--set-file configMaps.html-content.data.index\.html=./index.html \
|
||||
--set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
```
|
||||
|
||||
### 5. Installazione in un namespace specifico
|
||||
|
||||
```bash
|
||||
kubectl create namespace calcolatore
|
||||
helm install calcolatore-prezzi ./helm-chart -n calcolatore
|
||||
```
|
||||
|
||||
## Configurazione
|
||||
|
||||
I seguenti parametri possono essere configurati nel file `values.yaml`:
|
||||
|
||||
### Parametri Applicazione
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `replicaCount` | Numero di repliche del pod | `2` |
|
||||
| `image.repository` | Repository dell'immagine Docker | `nginx` |
|
||||
| `image.tag` | Tag dell'immagine Docker | `alpine` |
|
||||
| `image.pullPolicy` | Policy di pull dell'immagine | `IfNotPresent` |
|
||||
|
||||
### Parametri Service
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `service.type` | Tipo di service Kubernetes | `ClusterIP` |
|
||||
| `service.port` | Porta del service | `80` |
|
||||
| `service.targetPort` | Porta target del container | `80` |
|
||||
|
||||
### Parametri Ingress
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `ingress.enabled` | Abilita l'ingress | `true` |
|
||||
| `ingress.className` | Classe dell'ingress controller | `nginx` |
|
||||
| `ingress.hosts[0].host` | Hostname per l'ingress | `calcolatore.example.com` |
|
||||
| `ingress.tls[0].secretName` | Nome del secret TLS | `calcolatore-tls` |
|
||||
|
||||
### Parametri Risorse
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `resources.limits.cpu` | Limite CPU | `200m` |
|
||||
| `resources.limits.memory` | Limite memoria | `256Mi` |
|
||||
| `resources.requests.cpu` | Request CPU | `100m` |
|
||||
| `resources.requests.memory` | Request memoria | `128Mi` |
|
||||
|
||||
### Parametri Autoscaling
|
||||
|
||||
| Parametro | Descrizione | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `autoscaling.enabled` | Abilita l'HPA | `false` |
|
||||
| `autoscaling.minReplicas` | Numero minimo di repliche | `2` |
|
||||
| `autoscaling.maxReplicas` | Numero massimo di repliche | `5` |
|
||||
| `autoscaling.targetCPUUtilizationPercentage` | Target CPU per scaling | `80` |
|
||||
|
||||
## Esempi di Configurazione
|
||||
|
||||
### values-production.yaml
|
||||
|
||||
```yaml
|
||||
replicaCount: 3
|
||||
|
||||
image:
|
||||
repository: your-registry.io/calcolatore-prezzi
|
||||
tag: "1.0.0"
|
||||
pullPolicy: Always
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
hosts:
|
||||
- host: calcolatore.yourdomain.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: calcolatore-prod-tls
|
||||
hosts:
|
||||
- calcolatore.yourdomain.com
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 2
|
||||
```
|
||||
|
||||
### values-development.yaml
|
||||
|
||||
```yaml
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
tag: "alpine"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
hosts:
|
||||
- host: calcolatore.dev.local
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls: []
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
## Aggiornamento
|
||||
|
||||
Per aggiornare il deployment:
|
||||
|
||||
```bash
|
||||
helm upgrade calcolatore-prezzi ./helm-chart
|
||||
```
|
||||
|
||||
Con file HTML aggiornati:
|
||||
|
||||
```bash
|
||||
helm upgrade calcolatore-prezzi ./helm-chart \
|
||||
--set-file configMaps.html-content.data.index\.html=./index.html \
|
||||
--set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
```
|
||||
|
||||
## Disinstallazione
|
||||
|
||||
```bash
|
||||
helm uninstall calcolatore-prezzi
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Per testare il chart senza installarlo:
|
||||
|
||||
```bash
|
||||
# Dry run
|
||||
helm install calcolatore-prezzi ./helm-chart --dry-run --debug
|
||||
|
||||
# Template rendering
|
||||
helm template calcolatore-prezzi ./helm-chart
|
||||
|
||||
# Lint
|
||||
helm lint ./helm-chart
|
||||
```
|
||||
|
||||
## Deploy con CI/CD
|
||||
|
||||
### GitLab CI Example
|
||||
|
||||
```yaml
|
||||
deploy:
|
||||
stage: deploy
|
||||
image: alpine/helm:latest
|
||||
script:
|
||||
- helm dependency update ./helm-chart
|
||||
- helm upgrade --install calcolatore-prezzi ./helm-chart
|
||||
--namespace production
|
||||
--create-namespace
|
||||
--set image.tag=$CI_COMMIT_SHA
|
||||
--set-file configMaps.html-content.data.index\.html=./index.html
|
||||
--set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Deploy to Kubernetes
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v1
|
||||
with:
|
||||
version: '3.9.0'
|
||||
|
||||
- name: Deploy
|
||||
run: |
|
||||
helm dependency update ./helm-chart
|
||||
helm upgrade --install calcolatore-prezzi ./helm-chart \
|
||||
--namespace production \
|
||||
--create-namespace \
|
||||
--set-file configMaps.html-content.data.index\.html=./index.html \
|
||||
--set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Verificare lo stato del deployment
|
||||
|
||||
```bash
|
||||
kubectl get pods -l app.kubernetes.io/name=calcolatore-prezzi
|
||||
kubectl describe pod <pod-name>
|
||||
kubectl logs <pod-name>
|
||||
```
|
||||
|
||||
### Verificare la configurazione
|
||||
|
||||
```bash
|
||||
helm get values calcolatore-prezzi
|
||||
helm get manifest calcolatore-prezzi
|
||||
```
|
||||
|
||||
### Verificare l'ingress
|
||||
|
||||
```bash
|
||||
kubectl get ingress
|
||||
kubectl describe ingress calcolatore-prezzi
|
||||
```
|
||||
|
||||
## Note
|
||||
|
||||
- Questo chart usa il base-helm come dipendenza per standardizzare le risorse Kubernetes
|
||||
- I file HTML vengono iniettati tramite ConfigMap
|
||||
- Per ambienti di produzione, considera l'uso di un registry Docker privato e immagini custom
|
||||
- Assicurati di configurare correttamente i certificati SSL per la produzione
|
||||
|
||||
## Supporto
|
||||
|
||||
Per problemi o domande, apri una issue nel repository del progetto.
|
||||
27
helm-chart/templates/NOTES.txt
Normal file
27
helm-chart/templates/NOTES.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "common.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "common.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "common.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "common.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
|
||||
{{- end }}
|
||||
|
||||
2. The Calcolatore Prezzi Software application has been deployed!
|
||||
- Main menu: /index.html
|
||||
- Project mode: /project-mode.html
|
||||
- Shop mode: /shop-mode.html
|
||||
60
helm-chart/templates/_helpers.tpl
Normal file
60
helm-chart/templates/_helpers.tpl
Normal file
@@ -0,0 +1,60 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "calcolatore.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
*/}}
|
||||
{{- define "calcolatore.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "calcolatore.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "calcolatore.labels" -}}
|
||||
helm.sh/chart: {{ include "calcolatore.chart" . }}
|
||||
{{ include "calcolatore.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "calcolatore.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "calcolatore.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "calcolatore.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "calcolatore.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
53
helm-chart/templates/configmap.yaml
Normal file
53
helm-chart/templates/configmap.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "calcolatore.fullname" . }}-html-content
|
||||
labels:
|
||||
{{- include "calcolatore.labels" . | nindent 4 }}
|
||||
data:
|
||||
# Note: In a real deployment, you would use a CI/CD pipeline to inject these files
|
||||
# or mount them from a separate volume. This is just a simple example.
|
||||
# You can also use --set-file flag with helm to inject files:
|
||||
# helm install myapp ./helm-chart \
|
||||
# --set-file configMaps.html-content.data.index\.html=./index.html \
|
||||
# --set-file configMaps.html-content.data.project-mode\.html=./project-mode.html \
|
||||
# --set-file configMaps.html-content.data.shop-mode\.html=./shop-mode.html
|
||||
|
||||
index.html: |
|
||||
<!-- Menu/Landing page - Content will be injected during deployment -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Calcolatore Prezzi - Selezione Modalità</title>
|
||||
<meta http-equiv="refresh" content="0; url=https://example.com/">
|
||||
</head>
|
||||
<body>
|
||||
<p>Loading...</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
project-mode.html: |
|
||||
<!-- Project mode - Content will be injected during deployment -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Calcolatore Prezzi - Modalità Progetto</title>
|
||||
<meta http-equiv="refresh" content="0; url=https://example.com/project-mode.html">
|
||||
</head>
|
||||
<body>
|
||||
<p>Loading...</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
shop-mode.html: |
|
||||
<!-- Shop mode - Content will be injected during deployment -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Calcolatore Prezzi - Modalità Negozio</title>
|
||||
<meta http-equiv="refresh" content="0; url=https://example.com/shop-mode.html">
|
||||
</head>
|
||||
<body>
|
||||
<p>Loading...</p>
|
||||
</body>
|
||||
</html>
|
||||
187
helm-chart/values.yaml
Normal file
187
helm-chart/values.yaml
Normal file
@@ -0,0 +1,187 @@
|
||||
# Default values for calcolatore-prezzi-software
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# Use base-helm common chart for standard configurations
|
||||
common:
|
||||
# Name override
|
||||
nameOverride: ""
|
||||
fullnameOverride: "calcolatore-prezzi"
|
||||
|
||||
# Application configuration
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
repository: nginx
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "alpine"
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 2000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
annotations: {}
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
hosts:
|
||||
- host: calcolatore.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: calcolatore-tls
|
||||
hosts:
|
||||
- calcolatore.example.com
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 80
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app.kubernetes.io/name
|
||||
operator: In
|
||||
values:
|
||||
- calcolatore-prezzi
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
# Liveness and Readiness probes
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
|
||||
# Volume mounts for static files
|
||||
volumeMounts:
|
||||
- name: html-content
|
||||
mountPath: /usr/share/nginx/html
|
||||
readOnly: true
|
||||
- name: nginx-cache
|
||||
mountPath: /var/cache/nginx
|
||||
- name: nginx-run
|
||||
mountPath: /var/run
|
||||
|
||||
volumes:
|
||||
- name: html-content
|
||||
configMap:
|
||||
name: calcolatore-html-content
|
||||
- name: nginx-cache
|
||||
emptyDir: {}
|
||||
- name: nginx-run
|
||||
emptyDir: {}
|
||||
|
||||
# ConfigMap for HTML files
|
||||
configMaps:
|
||||
html-content:
|
||||
data:
|
||||
# The HTML files will be injected here
|
||||
# In production, you would use a CI/CD pipeline to update these
|
||||
index.html: |
|
||||
<!-- Your index.html content will be here -->
|
||||
shop-mode.html: |
|
||||
<!-- Your shop-mode.html content will be here -->
|
||||
|
||||
# Environment variables
|
||||
env: []
|
||||
# - name: ENVIRONMENT
|
||||
# value: "production"
|
||||
|
||||
# Additional labels
|
||||
labels: {}
|
||||
|
||||
# Pod Disruption Budget
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
|
||||
# Network Policy
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: ingress-nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector: {}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
|
||||
# Monitoring
|
||||
monitoring:
|
||||
enabled: false
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
interval: 30s
|
||||
path: /metrics
|
||||
2044
index.html
2044
index.html
File diff suppressed because it is too large
Load Diff
1499
project-mode.html
Normal file
1499
project-mode.html
Normal file
File diff suppressed because it is too large
Load Diff
658
shop-mode.html
Normal file
658
shop-mode.html
Normal file
@@ -0,0 +1,658 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Calcolatore Prezzi - Modalità Negozio</title>
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
|
||||
<!-- jsPDF per generazione PDF -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
|
||||
<!-- Font Awesome per icone -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-slide-in {
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-emerald-400 via-teal-500 to-cyan-500 min-h-screen" x-data="shopApp()" x-init="init()">
|
||||
<div class="container mx-auto p-4 max-w-7xl">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="glass rounded-2xl p-8 mb-6 animate-slide-in text-center">
|
||||
<h1 class="text-4xl font-bold text-gray-800 flex items-center justify-center gap-3">
|
||||
<i class="fas fa-store text-emerald-600"></i>
|
||||
Calcolatore Prezzi - Modalità Negozio
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-2">Sistema di preventivi basato su articoli e quantità</p>
|
||||
</div>
|
||||
|
||||
<!-- Notifiche Toast -->
|
||||
<div x-show="notification.show"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform translate-y-2"
|
||||
x-transition:enter-end="opacity-100 transform translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed top-4 right-4 z-50">
|
||||
<div :class="'p-4 rounded-lg shadow-lg flex items-center gap-3 ' +
|
||||
(notification.type === 'success' ? 'bg-green-500' :
|
||||
notification.type === 'error' ? 'bg-red-500' :
|
||||
notification.type === 'warning' ? 'bg-orange-500' : 'bg-blue-500') +
|
||||
' text-white'">
|
||||
<i :class="'fas fa-' +
|
||||
(notification.type === 'success' ? 'check-circle' :
|
||||
notification.type === 'error' ? 'exclamation-circle' :
|
||||
notification.type === 'warning' ? 'exclamation-triangle' : 'info-circle')"></i>
|
||||
<span x-text="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dati Venditore -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-building text-emerald-600"></i>
|
||||
Dati Venditore
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.nome"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Nome Azienda">
|
||||
<i class="fas fa-signature absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.piva"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="P.IVA">
|
||||
<i class="fas fa-id-card absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.indirizzo"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Indirizzo">
|
||||
<i class="fas fa-map-marker-alt absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="venditore.telefono"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Telefono">
|
||||
<i class="fas fa-phone absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dati Cliente -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-user text-emerald-600"></i>
|
||||
Dati Cliente
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="relative">
|
||||
<input type="text" x-model="cliente.nome"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Nome Cliente">
|
||||
<i class="fas fa-user-tie absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="email" x-model="cliente.email"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Email">
|
||||
<i class="fas fa-envelope absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" x-model="cliente.telefono"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all"
|
||||
placeholder="Telefono">
|
||||
<i class="fas fa-phone absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="date" x-model="cliente.data"
|
||||
class="w-full p-3 pl-10 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none transition-all">
|
||||
<i class="fas fa-calendar absolute left-3 top-4 text-emerald-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Catalogo Prodotti -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-boxes text-emerald-600"></i>
|
||||
Catalogo Articoli
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3 mb-4">
|
||||
<template x-for="(item, index) in items" :key="item.id">
|
||||
<div class="p-4 bg-gradient-to-r from-emerald-50 to-teal-50 rounded-lg border-2 border-emerald-200">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex-1 grid grid-cols-1 md:grid-cols-5 gap-3">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Nome Articolo</label>
|
||||
<input type="text" x-model="item.name"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="es. Licenza Software XYZ">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Codice SKU</label>
|
||||
<input type="text" x-model="item.sku"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
placeholder="SKU-001">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Prezzo Unitario (€)</label>
|
||||
<input type="number" x-model.number="item.price"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="0.01" min="0">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Quantità</label>
|
||||
<input type="number" x-model.number="item.quantity"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="1" min="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
<button @click="removeItem(index)"
|
||||
class="text-red-500 hover:text-red-700 transition-colors">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
<div class="text-right">
|
||||
<p class="text-xs text-gray-600">Totale</p>
|
||||
<p class="text-lg font-bold text-emerald-600" x-text="'€' + (item.price * item.quantity).toFixed(2)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Descrizione</label>
|
||||
<textarea x-model="item.description"
|
||||
class="w-full p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
rows="2"
|
||||
placeholder="Descrizione dettagliata dell'articolo"></textarea>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-700 mb-1">Sconto (%)</label>
|
||||
<input type="number" x-model.number="item.discount"
|
||||
class="w-24 p-2 text-sm border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="1" min="0" max="100">
|
||||
</div>
|
||||
<div class="flex-1 text-right">
|
||||
<span class="text-xs text-gray-600">Prezzo scontato: </span>
|
||||
<span class="font-bold text-emerald-600" x-text="'€' + calculateDiscountedTotal(item).toFixed(2)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button @click="addItem()"
|
||||
class="w-full bg-gradient-to-r from-emerald-500 to-teal-500 text-white font-bold py-3 px-6 rounded-lg hover:from-emerald-600 hover:to-teal-600 transition-all flex items-center justify-center gap-2">
|
||||
<i class="fas fa-plus"></i>
|
||||
Aggiungi Articolo
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Configurazione Fattura -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-cog text-emerald-600"></i>
|
||||
Configurazione Fattura
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">IVA (%)</label>
|
||||
<select x-model.number="taxRate"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none">
|
||||
<option value="0">Esente IVA (0%)</option>
|
||||
<option value="4">4% (Beni di prima necessità)</option>
|
||||
<option value="10">10% (Ridotta)</option>
|
||||
<option value="22">22% (Ordinaria)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Spese di Spedizione (€)</label>
|
||||
<input type="number" x-model.number="shippingCost"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
step="0.01" min="0">
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Note Fattura</label>
|
||||
<textarea x-model="invoiceNotes"
|
||||
class="w-full p-3 border-2 border-emerald-200 rounded-lg focus:border-emerald-500 focus:outline-none"
|
||||
rows="3"
|
||||
placeholder="Inserisci eventuali note o condizioni di pagamento"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Riepilogo Totali -->
|
||||
<div class="glass rounded-2xl p-6 mb-6 animate-slide-in">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2">
|
||||
<i class="fas fa-calculator text-emerald-600"></i>
|
||||
Riepilogo Preventivo
|
||||
</h2>
|
||||
|
||||
<div class="bg-gradient-to-br from-emerald-50 to-teal-100 p-6 rounded-xl shadow-lg">
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded">
|
||||
<span class="text-gray-700">Subtotale Articoli</span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateSubtotal())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="calculateTotalDiscount() > 0">
|
||||
<span class="text-orange-600">Sconto Totale</span>
|
||||
<span class="font-bold text-orange-600" x-text="'- ' + formatCurrency(calculateTotalDiscount())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="shippingCost > 0">
|
||||
<span class="text-gray-700">Spese di Spedizione</span>
|
||||
<span class="font-bold" x-text="formatCurrency(shippingCost)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded">
|
||||
<span class="text-gray-700">Imponibile</span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateTaxableAmount())"></span>
|
||||
</div>
|
||||
<div class="flex justify-between p-2 bg-white/70 rounded" x-show="taxRate > 0">
|
||||
<span class="text-gray-700" x-text="'IVA (' + taxRate + '%)'"></span>
|
||||
<span class="font-bold" x-text="formatCurrency(calculateTax())"></span>
|
||||
</div>
|
||||
<div class="border-t-2 border-emerald-300 pt-3 mt-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl font-bold text-emerald-800">TOTALE</span>
|
||||
<span class="text-3xl font-bold text-emerald-600" x-text="formatCurrency(calculateTotal())"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistiche -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mt-6">
|
||||
<div class="text-center p-4 bg-emerald-50 rounded-lg">
|
||||
<i class="fas fa-box text-2xl text-emerald-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Articoli</p>
|
||||
<p class="text-xl font-bold text-emerald-600" x-text="items.length"></p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-teal-50 rounded-lg">
|
||||
<i class="fas fa-layer-group text-2xl text-teal-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Quantità Totale</p>
|
||||
<p class="text-xl font-bold text-teal-600" x-text="calculateTotalQuantity()"></p>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-cyan-50 rounded-lg">
|
||||
<i class="fas fa-percentage text-2xl text-cyan-600 mb-2"></i>
|
||||
<p class="text-sm text-gray-600">Sconto Medio</p>
|
||||
<p class="text-xl font-bold text-cyan-600" x-text="calculateAverageDiscount().toFixed(1) + '%'"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pulsanti Azione -->
|
||||
<div class="glass rounded-2xl p-6 animate-slide-in">
|
||||
<div class="flex flex-wrap gap-3 justify-center">
|
||||
<button @click="generaPDF()"
|
||||
class="bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
Genera PDF
|
||||
</button>
|
||||
<button @click="salvaDati()"
|
||||
class="bg-teal-500 hover:bg-teal-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-save"></i>
|
||||
Salva
|
||||
</button>
|
||||
<button @click="caricaDati()"
|
||||
class="bg-cyan-500 hover:bg-cyan-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-upload"></i>
|
||||
Carica
|
||||
</button>
|
||||
<button @click="stampa()"
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-print"></i>
|
||||
Stampa
|
||||
</button>
|
||||
<button @click="resetForm()"
|
||||
class="bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg shadow-lg transform hover:scale-105 transition-all flex items-center gap-2">
|
||||
<i class="fas fa-redo"></i>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function shopApp() {
|
||||
return {
|
||||
notification: {
|
||||
show: false,
|
||||
message: '',
|
||||
type: 'info'
|
||||
},
|
||||
|
||||
venditore: {
|
||||
nome: '',
|
||||
piva: '',
|
||||
indirizzo: '',
|
||||
telefono: ''
|
||||
},
|
||||
|
||||
cliente: {
|
||||
nome: '',
|
||||
email: '',
|
||||
telefono: '',
|
||||
data: new Date().toISOString().split('T')[0]
|
||||
},
|
||||
|
||||
items: [],
|
||||
itemCounter: 0,
|
||||
|
||||
taxRate: 22,
|
||||
shippingCost: 0,
|
||||
invoiceNotes: '',
|
||||
|
||||
init() {
|
||||
this.addItem();
|
||||
},
|
||||
|
||||
addItem() {
|
||||
this.itemCounter++;
|
||||
this.items.push({
|
||||
id: this.itemCounter,
|
||||
name: '',
|
||||
sku: '',
|
||||
description: '',
|
||||
price: 0,
|
||||
quantity: 1,
|
||||
discount: 0
|
||||
});
|
||||
},
|
||||
|
||||
removeItem(index) {
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
|
||||
calculateDiscountedTotal(item) {
|
||||
const subtotal = item.price * item.quantity;
|
||||
const discount = subtotal * (item.discount / 100);
|
||||
return subtotal - discount;
|
||||
},
|
||||
|
||||
calculateSubtotal() {
|
||||
return this.items.reduce((sum, item) => {
|
||||
return sum + (item.price * item.quantity);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
calculateTotalDiscount() {
|
||||
return this.items.reduce((sum, item) => {
|
||||
const subtotal = item.price * item.quantity;
|
||||
return sum + (subtotal * (item.discount / 100));
|
||||
}, 0);
|
||||
},
|
||||
|
||||
calculateTaxableAmount() {
|
||||
const subtotal = this.calculateSubtotal();
|
||||
const discount = this.calculateTotalDiscount();
|
||||
return subtotal - discount + this.shippingCost;
|
||||
},
|
||||
|
||||
calculateTax() {
|
||||
return this.calculateTaxableAmount() * (this.taxRate / 100);
|
||||
},
|
||||
|
||||
calculateTotal() {
|
||||
return this.calculateTaxableAmount() + this.calculateTax();
|
||||
},
|
||||
|
||||
calculateTotalQuantity() {
|
||||
return this.items.reduce((sum, item) => sum + item.quantity, 0);
|
||||
},
|
||||
|
||||
calculateAverageDiscount() {
|
||||
if (this.items.length === 0) return 0;
|
||||
const totalDiscount = this.items.reduce((sum, item) => sum + item.discount, 0);
|
||||
return totalDiscount / this.items.length;
|
||||
},
|
||||
|
||||
formatCurrency(value) {
|
||||
return `€${value.toFixed(2)}`;
|
||||
},
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
this.notification = {
|
||||
show: true,
|
||||
message: message,
|
||||
type: type
|
||||
};
|
||||
setTimeout(() => {
|
||||
this.notification.show = false;
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
generaPDF() {
|
||||
const { jsPDF } = window.jspdf;
|
||||
const doc = new jsPDF();
|
||||
|
||||
let yPos = 20;
|
||||
|
||||
// Header
|
||||
doc.setFontSize(20);
|
||||
doc.setTextColor(16, 185, 129);
|
||||
doc.text(this.venditore.nome || 'Venditore', 105, yPos, { align: 'center' });
|
||||
yPos += 10;
|
||||
|
||||
doc.setFontSize(14);
|
||||
doc.setTextColor(100);
|
||||
doc.text('PREVENTIVO', 105, yPos, { align: 'center' });
|
||||
yPos += 15;
|
||||
|
||||
// Dati venditore e cliente
|
||||
doc.setFontSize(10);
|
||||
doc.setTextColor(0);
|
||||
doc.text('VENDITORE:', 20, yPos);
|
||||
doc.text('CLIENTE:', 120, yPos);
|
||||
yPos += 5;
|
||||
|
||||
doc.setFontSize(9);
|
||||
doc.text(this.venditore.nome, 20, yPos);
|
||||
doc.text(this.cliente.nome, 120, yPos);
|
||||
yPos += 5;
|
||||
doc.text(`P.IVA: ${this.venditore.piva}`, 20, yPos);
|
||||
doc.text(this.cliente.email, 120, yPos);
|
||||
yPos += 5;
|
||||
doc.text(this.venditore.indirizzo, 20, yPos);
|
||||
doc.text(this.cliente.telefono, 120, yPos);
|
||||
yPos += 10;
|
||||
|
||||
// Tabella articoli
|
||||
doc.setFontSize(10);
|
||||
doc.setFillColor(16, 185, 129);
|
||||
doc.rect(20, yPos, 170, 7, 'F');
|
||||
doc.setTextColor(255);
|
||||
doc.text('Articolo', 25, yPos + 5);
|
||||
doc.text('Q.tà', 120, yPos + 5);
|
||||
doc.text('Prezzo', 140, yPos + 5);
|
||||
doc.text('Sconto', 160, yPos + 5);
|
||||
doc.text('Totale', 175, yPos + 5);
|
||||
yPos += 10;
|
||||
|
||||
doc.setTextColor(0);
|
||||
this.items.forEach(item => {
|
||||
if (yPos > 270) {
|
||||
doc.addPage();
|
||||
yPos = 20;
|
||||
}
|
||||
doc.text(item.name.substring(0, 40), 25, yPos);
|
||||
doc.text(String(item.quantity), 125, yPos);
|
||||
doc.text(`€${item.price.toFixed(2)}`, 140, yPos);
|
||||
doc.text(`${item.discount}%`, 163, yPos);
|
||||
doc.text(`€${this.calculateDiscountedTotal(item).toFixed(2)}`, 175, yPos);
|
||||
yPos += 7;
|
||||
});
|
||||
|
||||
yPos += 5;
|
||||
|
||||
// Totali
|
||||
doc.line(120, yPos, 190, yPos);
|
||||
yPos += 7;
|
||||
|
||||
doc.text('Subtotale:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateSubtotal()), 175, yPos);
|
||||
yPos += 7;
|
||||
|
||||
if (this.calculateTotalDiscount() > 0) {
|
||||
doc.text('Sconto:', 120, yPos);
|
||||
doc.text(`-${this.formatCurrency(this.calculateTotalDiscount())}`, 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
if (this.shippingCost > 0) {
|
||||
doc.text('Spedizione:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.shippingCost), 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
doc.text('Imponibile:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTaxableAmount()), 175, yPos);
|
||||
yPos += 7;
|
||||
|
||||
if (this.taxRate > 0) {
|
||||
doc.text(`IVA (${this.taxRate}%):`, 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTax()), 175, yPos);
|
||||
yPos += 7;
|
||||
}
|
||||
|
||||
doc.setFontSize(12);
|
||||
doc.setFont(undefined, 'bold');
|
||||
doc.text('TOTALE:', 120, yPos);
|
||||
doc.text(this.formatCurrency(this.calculateTotal()), 175, yPos);
|
||||
|
||||
const filename = `Preventivo_${this.cliente.nome || 'Cliente'}_${new Date().toISOString().split('T')[0]}.pdf`;
|
||||
doc.save(filename);
|
||||
|
||||
this.showNotification('PDF generato con successo!', 'success');
|
||||
},
|
||||
|
||||
salvaDati() {
|
||||
const data = {
|
||||
venditore: this.venditore,
|
||||
cliente: this.cliente,
|
||||
items: this.items,
|
||||
taxRate: this.taxRate,
|
||||
shippingCost: this.shippingCost,
|
||||
invoiceNotes: this.invoiceNotes,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const dataStr = JSON.stringify(data, null, 2);
|
||||
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
||||
const clientName = this.cliente.nome || 'preventivo';
|
||||
const exportFileDefaultName = `${clientName}_${new Date().toISOString().split('T')[0]}.json`;
|
||||
|
||||
const linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', dataUri);
|
||||
linkElement.setAttribute('download', exportFileDefaultName);
|
||||
linkElement.click();
|
||||
|
||||
this.showNotification('Dati salvati con successo!', 'success');
|
||||
},
|
||||
|
||||
caricaDati() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.target.result);
|
||||
this.venditore = data.venditore || this.venditore;
|
||||
this.cliente = data.cliente || this.cliente;
|
||||
this.items = data.items || [];
|
||||
this.taxRate = data.taxRate || 22;
|
||||
this.shippingCost = data.shippingCost || 0;
|
||||
this.invoiceNotes = data.invoiceNotes || '';
|
||||
this.showNotification('Dati caricati con successo!', 'success');
|
||||
} catch (error) {
|
||||
this.showNotification('Errore nel caricamento dei dati', 'error');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
|
||||
stampa() {
|
||||
window.print();
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
if (confirm('Sei sicuro di voler resettare tutti i campi?')) {
|
||||
this.cliente = {
|
||||
nome: '',
|
||||
email: '',
|
||||
telefono: '',
|
||||
data: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
this.items = [];
|
||||
this.shippingCost = 0;
|
||||
this.invoiceNotes = '';
|
||||
this.addItem();
|
||||
this.showNotification('Form resettato!', 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user