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
|
||||||
67
Dockerfile
67
Dockerfile
@@ -1,7 +1,70 @@
|
|||||||
|
# Multi-stage build for optimized nginx image
|
||||||
FROM nginx:alpine
|
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
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
# 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
|
# 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
|
## Caratteristiche
|
||||||
|
|
||||||
- Interfaccia utente moderna e responsive
|
### Modalità Standard
|
||||||
- Calcolo automatico dei prezzi basato su parametri configurabili
|
|
||||||
- Supporto per diverse metodologie di stima
|
|
||||||
- Design ottimizzato per il mercato italiano
|
|
||||||
|
|
||||||
## 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:**
|
- ✅ Gestione catalogo articoli con codici SKU
|
||||||
```bash
|
- ✅ Calcolo prezzi unitari e quantità
|
||||||
docker build -t git.commandware.com/dnviti/calcolatore_prezzi_software .
|
- ✅ Sconti per articolo
|
||||||
```
|
- ✅ Gestione spese di spedizione
|
||||||
|
- ✅ Calcolo IVA configurabile
|
||||||
2. **Eseguire il container:**
|
- ✅ Generazione PDF preventivo
|
||||||
```bash
|
- ✅ Interfaccia intuitiva per vendita prodotti/servizi
|
||||||
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
|
|
||||||
|
|
||||||
## Struttura del Progetto
|
## Struttura del Progetto
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── index.html # Applicazione web principale
|
├── index.html # Menu principale per selezione modalità
|
||||||
├── Dockerfile # Configurazione Docker
|
├── project-mode.html # Modalità progetto (ore/persone/milestone)
|
||||||
├── .gitea/
|
├── shop-mode.html # Modalità negozio (articoli/quantità)
|
||||||
│ └── workflows/
|
├── Dockerfile # Build immagine Docker
|
||||||
│ └── build.yml # Pipeline CI/CD
|
├── deploy.sh # Script deploy automatico
|
||||||
└── README.md # Documentazione
|
├── .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
|
## Tecnologie Utilizzate
|
||||||
|
|
||||||
- HTML5
|
- **Frontend:** HTML5, CSS3, JavaScript (Vanilla + Alpine.js)
|
||||||
- CSS3 (Tailwind CSS)
|
- **Styling:** Tailwind CSS (via CDN)
|
||||||
- JavaScript
|
- **PDF Generation:** jsPDF
|
||||||
- Nginx (per serving statico)
|
- **Icons:** Font Awesome
|
||||||
- Docker
|
- **Containerization:** Docker
|
||||||
- Gitea Actions
|
- **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
|
## 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
|
||||||
2054
index.html
2054
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