Compare commits

...

196 Commits

Author SHA1 Message Date
morgane bcecc2430d Actualiser Dockerfile
CI / PHP Syntax Check (push) Waiting to run
CI / JavaScript Lint (push) Waiting to run
CI / Docker Build Test (push) Waiting to run
CI / Validate Translation Files (push) Waiting to run
CI / Auto-merge develop → main (push) Blocked by required conditions
CI / Create GitHub Release (push) Blocked by required conditions
Security Scan (Trivy) / Trivy — Docker image scan (push) Waiting to run
Security Scan (Trivy) / Trivy — Filesystem scan (push) Waiting to run
2026-06-30 11:37:51 +00:00
morgane 33d5c4c370 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Waiting to run
CI / JavaScript Lint (push) Waiting to run
CI / Docker Build Test (push) Waiting to run
CI / Validate Translation Files (push) Waiting to run
CI / Auto-merge develop → main (push) Blocked by required conditions
CI / Create GitHub Release (push) Blocked by required conditions
2026-06-29 18:22:25 +00:00
morgane ddef591108 Actualiser .htaccess
CI / PHP Syntax Check (push) Waiting to run
CI / JavaScript Lint (push) Waiting to run
CI / Docker Build Test (push) Waiting to run
CI / Validate Translation Files (push) Waiting to run
CI / Auto-merge develop → main (push) Blocked by required conditions
CI / Create GitHub Release (push) Blocked by required conditions
2026-06-29 15:44:24 +00:00
morgane ca98acc1f2 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 17:49:17 +00:00
morgane 616818998d Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 17:43:03 +00:00
morgane ef73630cad Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 17:31:38 +00:00
morgane a6369765b0 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 17:16:56 +00:00
morgane 5a50403e52 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 17:05:42 +00:00
morgane 865cb561be Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 16:57:43 +00:00
morgane 18169417d3 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 16:49:56 +00:00
morgane 22dd77c879 Actualiser README.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 16:28:57 +00:00
morgane bcbdf669a1 Actualiser README.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 16:28:06 +00:00
morgane c513b0b4ef Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-27 16:25:46 +00:00
morgane 3bfa89e61d Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-25 20:21:34 +00:00
morgane 427bcd20a4 Actualiser docker-compose.yml
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-24 17:05:55 +00:00
morgane 9b026408e2 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-21 15:31:32 +00:00
morgane 5abf5f9adf Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-21 15:30:02 +00:00
morgane e58eb0501a Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-20 09:06:50 +00:00
morgane ecf7ec53fd Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-20 08:59:36 +00:00
morgane 78310f9fe9 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-20 08:50:31 +00:00
morgane b73748346e Actualiser manifest.json
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-20 08:08:12 +00:00
morgane 833afb3cfd Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 16:32:41 +00:00
morgane 726d371d26 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 16:30:31 +00:00
morgane a4267ee420 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 16:29:00 +00:00
morgane fef70cb97c Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 16:20:53 +00:00
morgane ca285f7a9d Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 16:19:56 +00:00
morgane 8176237b93 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-19 15:50:05 +00:00
morgane 5ce935de49 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 17:30:45 +00:00
morgane 751b18ba3c Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 17:20:16 +00:00
morgane 55d562e0a3 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 17:14:19 +00:00
morgane 7c3fb41b43 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 17:09:55 +00:00
morgane a84824327d Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:59:24 +00:00
morgane 409ea5d2e5 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:57:10 +00:00
morgane 0de44ae341 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 16:56:24 +00:00
morgane 5a4e16c30d Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:49:19 +00:00
morgane cfcc3ce49f Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:47:57 +00:00
morgane 3965c6ef44 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 16:47:10 +00:00
morgane 99b65900c4 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 16:46:19 +00:00
morgane 1505550a16 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:24:29 +00:00
morgane b23edc39b3 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 16:13:13 +00:00
morgane 371dda46f0 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 15:50:41 +00:00
morgane 3c68ce0dd1 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 15:47:45 +00:00
morgane 94f5649183 Actualiser translations/fr.json
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 14:07:40 +00:00
morgane cff250055c Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 13:33:59 +00:00
morgane 477978aed9 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 13:26:15 +00:00
morgane bf08556556 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 13:25:29 +00:00
morgane 594c9fa115 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 13:06:56 +00:00
morgane 8c471c1b7f Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 13:05:58 +00:00
morgane 9075740454 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 13:03:41 +00:00
morgane 3f242c1836 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 13:02:58 +00:00
morgane 9c97798a21 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 12:57:25 +00:00
morgane 0fb887756f Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 12:55:21 +00:00
morgane 37bf403412 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 12:54:20 +00:00
morgane 3676793194 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 12:53:49 +00:00
morgane 3f410022a7 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 12:53:14 +00:00
morgane 7631018929 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 12:52:51 +00:00
morgane bb16e441f5 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 11:40:33 +00:00
morgane bacb98b4eb Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 11:12:44 +00:00
morgane b739cb3a47 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 11:05:03 +00:00
morgane 754daa9989 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 09:07:17 +00:00
morgane 80a915ac35 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 08:58:56 +00:00
morgane b28c6591a9 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 08:58:15 +00:00
morgane 1e40da7235 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 08:56:27 +00:00
morgane 046355d6b0 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 08:55:35 +00:00
morgane 3a33dc7173 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 08:44:06 +00:00
morgane c0c1a312c6 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 08:43:08 +00:00
morgane 8ae455d82c Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 08:38:06 +00:00
morgane 0c8eee404e Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-18 08:36:21 +00:00
morgane e746c3d05b Actualiser README.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 06:49:37 +00:00
morgane 6f1966113d Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-18 06:31:37 +00:00
morgane f1e07f4151 Actualiser README.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 22:32:45 +00:00
morgane 7d4b881d7c Supprimer README.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 22:32:30 +00:00
morgane 63faf402c2 Ajouter READMI.md
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 22:32:17 +00:00
morgane 8543106fed Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 22:26:09 +00:00
morgane 4d1a4be0ea Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Commentaires de la ligne modifier la fiche produit (catégorie)
Ligne 20087
2026-06-17 22:17:16 +00:00
morgane 0065987050 Supprimer test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:46:16 +00:00
morgane ad101cc58f Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:41:10 +00:00
morgane 3a08929353 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:39:07 +00:00
morgane f935790ab2 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:38:04 +00:00
morgane 45ea60e305 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:36:45 +00:00
morgane d364588011 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:26:58 +00:00
morgane 9fc1dd3614 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:23:05 +00:00
morgane 68484b8323 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:22:21 +00:00
morgane e534ea2e96 Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:20:01 +00:00
morgane 08bb293963 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:16:52 +00:00
morgane ae3254c195 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 21:14:28 +00:00
morgane 77b053c573 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:13:07 +00:00
morgane ab79339e8c Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 21:10:27 +00:00
morgane 6cba7132d5 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:44:36 +00:00
morgane 32da374e57 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:42:07 +00:00
morgane 8fda93044d Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:34:28 +00:00
morgane a97907dbe1 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:28:29 +00:00
morgane 94eeba280a Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:24:11 +00:00
morgane 978088ae23 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:20:33 +00:00
morgane d4c5b5b97c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:15:50 +00:00
morgane 0ec2620926 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:13:28 +00:00
morgane f65f65d38f Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:06:17 +00:00
morgane f3c1fd2e7b Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 20:05:07 +00:00
morgane 69eb319aa1 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:59:26 +00:00
morgane 02bfc82e2b Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:51:49 +00:00
morgane b8e43388b6 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:46:28 +00:00
morgane 6e532ebdd1 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:45:11 +00:00
morgane 56a10bd1c4 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:40:02 +00:00
morgane 621fb4f96e Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:37:17 +00:00
morgane 57ec6af58c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:35:18 +00:00
morgane 11886578ab Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:33:41 +00:00
morgane a02abce26e Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:28:50 +00:00
morgane 79d1ca06c7 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:28:02 +00:00
morgane d1ba63d9a0 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:25:55 +00:00
morgane 0ca4df0d27 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:23:56 +00:00
morgane fc324d55f5 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:22:09 +00:00
morgane 99c35e4c18 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:19:33 +00:00
morgane b12cd76acc Supprimer reconcile.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:19:27 +00:00
morgane 477139d47c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:12:37 +00:00
morgane 8b44432244 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 19:08:18 +00:00
morgane 4f1717c4b1 Ajouter reconcile.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 18:22:35 +00:00
morgane c2002977cd Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 18:00:35 +00:00
morgane f7fb4e8f33 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:57:44 +00:00
morgane ef9c26bed6 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:52:05 +00:00
morgane aaf4de5e6b Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:50:58 +00:00
morgane 56bc6a709f Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:48:35 +00:00
morgane b60994d745 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:45:21 +00:00
morgane 6e86c19262 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:43:46 +00:00
morgane 619b7b4517 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:42:18 +00:00
morgane ca2e39bc49 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:40:49 +00:00
morgane 42fcbef95b Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:39:18 +00:00
morgane 0619d1487c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:38:21 +00:00
morgane 79fff10b48 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:36:59 +00:00
morgane a6f59cabfb Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:35:47 +00:00
morgane eaf9ebc52e Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:33:07 +00:00
morgane df8211c8ac Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:29:12 +00:00
morgane 7fae0e08bf Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:27:18 +00:00
morgane 450095376c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:25:31 +00:00
morgane 257fa4797d Supprimer test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:25:23 +00:00
morgane 9acf952c10 Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:23:12 +00:00
morgane e0deb3481b Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:21:02 +00:00
morgane d35f975ab9 Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:18:05 +00:00
morgane 0502a4d132 Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:16:28 +00:00
morgane 2ea69fd223 Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:15:02 +00:00
morgane 01f28a2b09 Actualiser test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:12:47 +00:00
morgane 4bedfdd0f4 Ajouter test2.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:11:20 +00:00
morgane 6c63bb17a3 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:08:03 +00:00
morgane 665d3097e7 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:06:50 +00:00
morgane 43047276a6 Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:05:24 +00:00
morgane 51ad25926a Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:03:26 +00:00
morgane 33e552373c Actualiser test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 17:00:36 +00:00
morgane fc124c87bf Ajouter test.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:57:34 +00:00
morgane b166a305f2 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:55:39 +00:00
morgane 6377c45eaa Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:53:51 +00:00
morgane 8d18362d83 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:52:13 +00:00
morgane cabb8b28d4 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:50:34 +00:00
morgane 27257caa3e Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:46:48 +00:00
morgane 3d744f256a Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:43:50 +00:00
morgane 8e504cad28 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:41:39 +00:00
morgane e6b3328fe9 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:38:46 +00:00
morgane db7fc0df18 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:33:23 +00:00
morgane 3bb6dc7155 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:32:14 +00:00
morgane d75a6e76c6 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:29:19 +00:00
morgane b28aca5e55 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:28:49 +00:00
morgane 2fb782f8e1 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 16:23:16 +00:00
morgane c08798d462 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 14:10:10 +00:00
morgane f6fe0a55bd Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 14:00:42 +00:00
morgane fecbbafd38 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:59:24 +00:00
morgane c0ad69cf11 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:52:58 +00:00
morgane a2a1a5ba77 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:51:54 +00:00
morgane 98553c7600 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:49:34 +00:00
morgane 7d89d61b95 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:37:39 +00:00
morgane 6b1d5f4c45 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:34:20 +00:00
morgane 288e0c05f6 Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 13:30:48 +00:00
morgane e52aa6d699 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:27:42 +00:00
morgane 9512e3a8df Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 13:14:46 +00:00
morgane 4a729d2d10 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 13:04:08 +00:00
morgane 6ecb881d9f Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 12:59:46 +00:00
morgane 662c27d7b4 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 12:55:31 +00:00
morgane df9624ad75 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 12:54:33 +00:00
morgane df0a47e336 Actualiser assets/js/app.js
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 12:40:27 +00:00
morgane b97845553e Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 12:25:06 +00:00
morgane 495d7a22eb Actualiser api/database.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 10:30:37 +00:00
morgane a1511c608a Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 08:42:34 +00:00
morgane a3a3b54a85 Actualiser api/lib/env.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 08:26:35 +00:00
morgane 4ee2e5638b Actualiser backup.sh
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-17 08:07:28 +00:00
morgane a9f0527769 Actualiser Dockerfile
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled
2026-06-17 07:57:39 +00:00
morgane bf27f7f462 Actualiser index.html
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
2026-06-16 21:42:38 +00:00
dadaloop82 85ccdaa6f6 Release v1.7.42: shopping totals, waste learning, stability fixes.
Document waste reason picker, stable price estimates, DB retry, and kiosk CI fixes in CHANGELOG and README.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 12:50:13 +00:00
dadaloop82 16993135b9 Merge branch 'main' of github-evershelf:dadaloop82/EverShelf 2026-06-14 12:43:44 +00:00
dadaloop82 d1716fa6ff Fix shopping estimates, waste reasons, and recurring DB/timeouts.
Price each list line as one retail purchase; learn from discard reasons to cap restock suggestions. Retry inventory_use/shopping_add on SQLITE_BUSY; extend smart_shopping time limit. Reopen feature issues #98/#125; close auto-report bugs #201–#204.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 12:43:03 +00:00
github-actions[bot] 3ac42f7767 chore(kiosk): publish APK v1.7.19 for LAN OTA 2026-06-11 05:48:36 +00:00
dadaloop82 eb19265586 Kiosk: auto-discover on setup, LAN OTA, English-only GitHub triage.
Auto-run LAN discovery on server step; serve kiosk updates from releases/ via kiosk_update API; check LAN before GitHub for OTA in-place upgrades. Docker CI retries hub timeouts. Remove non-English feature issue comments; triage script English-only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 05:46:12 +00:00
dadaloop82 8a69e6d941 Fix kiosk LAN discovery and improve OTA update detection.
Discovery no longer aborts after 3s idle, probes priority hosts (.128, gateway) first, accepts ping API and normalizes HTTPS URLs. OTA compares versionCode from release notes; bump kiosk to 1.7.18.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 05:40:41 +00:00
dadaloop82 c5b0dbcf42 Fix inflated shopping price estimates and restore feature issues workflow.
Price each list line as one retail purchase instead of 14-day restock qty; convert €/kg AI prices to estimated piece weight; cap smart-shopping suggested conf/pz counts. Stop triage script from bulk-closing feature backlog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 05:34:56 +00:00
dadaloop82 338bd7ff66 Fix Kotlin string escaping in SetupActivity finishSetup JSON.
Correct broken replace() literals that prevented compileDebugKotlin from building.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 15:01:43 +00:00
dadaloop82 c7532f90cd Fix kiosk locale strings for Android resource compiler.
Escape apostrophes and normalize multiline strings in de/es/fr/it so assembleDebug no longer fails with invalid unicode escape sequences.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 14:54:20 +00:00
dadaloop82 5831e3bcea Release v1.7.41: fix Traefik startup and clean JSON API responses.
PHP 8.2 deprecations no longer corrupt health_check JSON; .htaccess
respects X-Forwarded-Proto behind reverse proxies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 12:32:10 +00:00
dadaloop82 ec1aae2a25 docs: note ops scripts in CHANGELOG 1.7.40
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 06:04:09 +00:00
dadaloop82 4f9f44e230 Add ops scripts and offline transformers bootstrap.
Maintenance CLI for finished-product/shopping audit, Bring sync, and
install-transformers-model.sh to fetch the Xenova classifier (model gitignored).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 06:03:24 +00:00
dadaloop82 9be8fb5cf3 Release v1.7.40: recipe fixes, DB lock retry, and Docker Traefik support.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 06:00:54 +00:00
41 changed files with 5695 additions and 1283 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules/
.git/
dist/
build/
*.log
+18 -2
View File
@@ -37,8 +37,10 @@ jobs:
id: version
run: |
VERSION=$(grep 'versionName' evershelf-kiosk/app/build.gradle.kts | grep -oP '"\K[^"]+')
VCODE=$(grep 'versionCode' evershelf-kiosk/app/build.gradle.kts | grep -oP '\d+')
echo "name=$VERSION" >> "$GITHUB_OUTPUT"
echo "Kiosk version: $VERSION"
echo "code=$VCODE" >> "$GITHUB_OUTPUT"
echo "Kiosk version: $VERSION (versionCode $VCODE)"
- name: Build debug APK
run: gradle assembleDebug --no-daemon
@@ -75,7 +77,21 @@ jobs:
sleep 3
gh release create kiosk-latest \
--title "EverShelf Kiosk Latest" \
--notes "Alias automatico → kiosk-${{ steps.version.outputs.name }}" \
--notes "Auto alias → kiosk-${{ steps.version.outputs.name }} (versionCode ${{ steps.version.outputs.code }})" \
--prerelease \
artifacts/evershelf-kiosk.apk
- name: Publish APK to releases/ for LAN OTA
env:
GH_TOKEN: ${{ secrets.WORKFLOW_PAT || secrets.GITHUB_TOKEN }}
run: |
cp artifacts/evershelf-kiosk.apk releases/evershelf-kiosk.apk
printf '{"version":"%s","version_code":%s}\n' \
"${{ steps.version.outputs.name }}" "${{ steps.version.outputs.code }}" \
> releases/kiosk-version.json
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add releases/evershelf-kiosk.apk releases/kiosk-version.json
git diff --staged --quiet || git commit -m "chore(kiosk): publish APK v${{ steps.version.outputs.name }} for LAN OTA"
git push origin HEAD:${{ github.ref_name }}
+12 -1
View File
@@ -43,7 +43,18 @@ jobs:
- uses: actions/checkout@v6
- name: Build Docker image
run: docker build -t evershelf-test .
run: |
set -e
for attempt in 1 2 3; do
echo "Docker build attempt $attempt/3..."
if docker build -t evershelf-test .; then
exit 0
fi
echo "Attempt $attempt failed — retrying in 20s..."
sleep 20
done
echo "Docker build failed after 3 attempts"
exit 1
- name: Test container starts
run: |
+1
View File
@@ -52,3 +52,4 @@ data/food_facts_cache.json
data/category_ai_cache.json
assets/img/logo/*_backup.*
logs/*.log
assets/vendor/transformers/Xenova/
+4 -3
View File
@@ -14,9 +14,10 @@ RewriteEngine On
Require all denied
</FilesMatch>
# Force HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Force HTTPS (skip when terminated TLS is forwarded — Traefik, Caddy, NPM, …)
#RewriteCond %{HTTPS} !=on
#RewriteCond %{HTTP:X-Forwarded-Proto} !^https$ [NC]
#RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# API routing
RewriteCond %{REQUEST_FILENAME} !-f
+42
View File
@@ -11,6 +11,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap.
## [1.7.42] - 2026-06-11
### Added
- **Waste reason picker** — Discarding a product prompts for why (expired, spoiled, wrong storage, kept too long, bought too much, forgotten, bad quality, other) in IT/EN/DE/FR/ES.
- **Waste learning** — Reasons are stored per product in `app_settings.waste_learning`; caps smart-shopping suggested quantities, surfaces preferred storage location, and tightens expiry alerts after repeated spoilage.
- **`scripts/github-issue-triage.php`** — Reopens wrongly closed feature backlog items; closes resolved auto-report bugs with English comments.
### Fixed
- **Inflated shopping total** — Price each Bring!/shopping line as **one retail purchase**; convert AI €/kg prices to estimated piece weight (200 g default) instead of multiplying by piece count; cap smart-shopping conf/pz suggestions used for pricing context.
- **SQLite database locked (#201#202)** — `inventory_use` and `shopping_add` (including Bring mode) wrapped in `dbWithRetry()`.
- **Smart shopping timeout (#203#204)** — `set_time_limit(120)` on `smartShopping()` / `smartShoppingCached()` for large inventories.
- **Android kiosk CI** — Escaped apostrophes in locale `strings.xml` (de/es/fr/it); fixed Kotlin JSON string escaping in `SetupActivity.finishSetup()`.
- **GitHub triage** — `triage-open-issues.php` no longer bulk-closes enhancement/feature backlog; reopened #98 (pin products) and #125 (cooking voice commands) where not yet implemented.
## [1.7.41] - 2026-06-08
### Fixed
- **Docker/Traefik “Impossibile contattare il server”** — PHP 8.2 deprecation notices (`LoggingPDO::prepare`) were emitted as HTML before JSON, breaking `fetch().json()` on the startup health check; API bootstrap now suppresses HTML error output in production.
- **Traefik HTTPS redirect loop** — `.htaccess` skips the HTTPS redirect when `X-Forwarded-Proto: https` is already set (compatible with Traefik `sslheader` middleware); no need to disable `.htaccess` manually.
- **LoggingPDO PHP 8.2** — `#[\ReturnTypeWillChange]` on `prepare()` to eliminate deprecation noise in error logs.
## [1.7.40] - 2026-06-08
### Added
- **Qty unit badges** — Quantity inputs show the active unit (g, ml, conf, pz, …) on use, add, recipe-use, edit and throw modals; scale live label “Inserimento in …”.
- **Recipe shopping suggestions** — AI recipes can list optional missing ingredients with one-tap add to Bring!/shopping list.
- **Recipe frozen badge** — Freezer items flagged in pantry lines and recipe UI; prompt rule for cooking from frozen.
- **Health check `db_writable`** — Startup diagnostic detects non-writable SQLite file (common Docker volume issue).
- **`scripts/triage-open-issues.php`** — Maintenance helper to comment/close GitHub issues via encrypted token.
- **Ops CLI scripts** — `audit-finished-shopping.php`, `backfill-finished-shopping.php`, `sync-shopping-bring.php`, `install-transformers-model.sh` (offline Xenova classifier bootstrap).
### Fixed
- **SQLite database locked** — `PRAGMA busy_timeout` 10s + `dbWithRetry()` on `inventory_update` under cron/PWA contention.
- **Barcode duplicate on save** — `saveProduct` merges or returns 409 instead of HTTP 500 on UNIQUE barcode.
- **EverLog CLI crash** — Safe cast of `REQUEST_METHOD` when null (kiosk/cron).
- **Spesa scan crash** — `currentPage``_currentPageId` in `_applySpesaScanUI`.
- **Recipe quantities** — Piece products use 1 pc base; serving caps for onions, leafy greens, minestrone; pantry-only post-processing; conf/g display fixes.
- **Smart shopping purchased block** — Server-side blocklist + spesa mode sync prevents cron from re-adding bought items.
### Changed
- **Docker behind Traefik** — Apache `SetEnvIf X-Forwarded-Proto https HTTPS=on` to avoid redirect loops.
## [1.7.39] - 2026-06-06
### Added
+8 -3
View File
@@ -6,10 +6,12 @@ RUN apt-get update && apt-get upgrade -y && apt-get install -y \
libcurl4-openssl-dev \
libonig-dev \
libgd-dev \
libzip-dev \
libicu-dev \
tesseract-ocr \
tesseract-ocr-ita \
tesseract-ocr-eng \
&& docker-php-ext-install pdo_sqlite curl mbstring gd \
&& docker-php-ext-install pdo_sqlite curl mbstring gd zip intl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Enable Apache mod_rewrite and mod_headers
@@ -28,12 +30,15 @@ RUN mkdir -p /var/www/html/data/backups \
# Create .env from example if it doesn't exist (will be overridden by volume mount)
RUN [ ! -f /var/www/html/.env ] && cp /var/www/html/.env.example /var/www/html/.env || true
RUN chown www-data:www-data /var/www/html/.env && chmod 664 /var/www/html/.env
# Apache configuration: serve from app root
RUN echo '<Directory /var/www/html>\n\
AllowOverride All\n\
Require all granted\n\
</Directory>' > /etc/apache2/conf-available/evershelf.conf \
</Directory>\n\
# Traefik / reverse-proxy: treat forwarded HTTPS as on so .htaccess does not redirect-loop\n\
SetEnvIf X-Forwarded-Proto "https" HTTPS=on' > /etc/apache2/conf-available/evershelf.conf \
&& a2enconf evershelf
# Expose port 80
@@ -43,4 +48,4 @@ EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
CMD ["apache2-foreground"]
CMD ["apache2-foreground"]
+157 -485
View File
@@ -1,551 +1,223 @@
# 🏠 EverShelf
# 🏠 EverShelf for Ricardo
> **Self-hosted pantry management system** — Track your food inventory, scan barcodes, get AI-powered recipe suggestions, and reduce waste.
---
<div align="center">
### 🚀 Try the live demo — no installation required!
**[▶ Open Live Demo](https://evershelfproject.dadaloop.it/demo)**
&nbsp;·&nbsp;
[🌐 Project Website](https://evershelfproject.dadaloop.it/)
&nbsp;·&nbsp;
[📖 Wiki](https://github.com/dadaloop82/EverShelf/wiki)
*The demo runs with mock pantry data. AI features are fully enabled. All write operations are safely sandboxed.*
</div>
> Fork personnalisé d'EverShelf, adapté pour servir de backend stock/recettes à **Ricardo**, l'application bartender. Garde toutes les fonctionnalités d'EverShelf, avec des ajustements pour la gestion de bar (catégorie boissons, sous-catégories alcools, intégration directe avec Ricardo).
---
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![PHP](https://img.shields.io/badge/PHP-8.0+-blue.svg)](https://www.php.net/)
[![SQLite](https://img.shields.io/badge/SQLite-3-blue.svg)](https://www.sqlite.org/)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](Dockerfile)
[![i18n](https://img.shields.io/badge/i18n-IT%20%7C%20EN%20%7C%20DE%20%7C%20FR%20%7C%20ES-orange.svg)](translations/)
[![Version](https://img.shields.io/badge/version-1.7.39-brightgreen.svg)](CHANGELOG.md)
[![GitHub stars](https://img.shields.io/github/stars/dadaloop82/EverShelf?style=social)](https://github.com/dadaloop82/EverShelf/stargazers)
[![Last commit](https://img.shields.io/github/last-commit/dadaloop82/EverShelf/main)](https://github.com/dadaloop82/EverShelf/commits/main)
[![Contributors](https://img.shields.io/github/contributors/dadaloop82/EverShelf)](https://github.com/dadaloop82/EverShelf/graphs/contributors)
[![GitHub Discussions](https://img.shields.io/github/discussions/dadaloop82/EverShelf)](https://github.com/dadaloop82/EverShelf/discussions)
[![CI](https://github.com/dadaloop82/EverShelf/actions/workflows/ci.yml/badge.svg)](https://github.com/dadaloop82/EverShelf/actions/workflows/ci.yml)
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/J3J01ZNETZ)
[![Docker](https://img.shields.io/badge/Docker-Compatible-2496ED.svg)](Dockerfile)
---
> **⚠️ Name disambiguation:** There is an unrelated iOS app also called **EverShelf**, developed and published by [Joshumi Technologies LLC](https://evershelf.joshumi.com/) on the [Apple App Store](https://apps.apple.com/app/evershelf/id6759439940). That application is a **completely separate, independent product** with no affiliation, association, or collaboration with this open-source project. This repository has no connection to Joshumi Technologies LLC, its products, or its services.
## ✨ Fonctionnalités principales
### 📦 Gestion des stocks
- Inventaire alimentaire complet
- Gestion des emplacements :
- 🏠 Placard
- ❄️ Réfrigérateur
- 🧊 Congélateur
- 📍 Emplacements personnalisés, entièrement gérés depuis une page **🔧 Configuration** dédiée (ajout, modification, suppression sans toucher au code)
- Sous-catégorie dédiée aux boissons (vin, bière, spiritueux, soda, jus, eau...) pour filtrer et trier l'inventaire plus précisément
- Scan de codes-barres avec la caméra du téléphone
- Ajout rapide de produits
- Suivi des dates de péremption
- Gestion des produits ouverts
- Support des produits sous vide
- Détection des incohérences de stock
---
## ✨ Features
## 🤖 Intelligence artificielle (Google Gemini)
### 🏠 NEW — Home Assistant Integration
EverShelf peut utiliser l'IA pour :
EverShelf has a **native Home Assistant integration** available on HACS.
Connect your pantry to your smart home in minutes — no YAML, no manual sensor setup.
- 📸 Identifier un produit depuis une photo
- 📅 Lire automatiquement une date limite de consommation
- 🧊 Proposer un stockage adapté
- 🍳 Générer des recettes selon votre inventaire
- 💬 Répondre aux questions sur vos produits
- 🛒 Améliorer les suggestions de courses
[![Install via HACS](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=dadaloop82&repository=ha-evershelf&category=integration)
&nbsp;
[![Add Integration](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=evershelf)
**What you get:**
| | |
|---|---|
| **16 sensors** | Expiry counts, stock levels by location (pantry / fridge / freezer), shopping list total, AI API usage, last backup timestamp, days to next expiry |
| **6 binary sensors** | Expired items, expiring items, expiring today, shopping list active, backup overdue, Bring! connected |
| **5 action buttons** | Refresh data, Refresh prices, **Suggest Recipe** (AI — result as HA notification), Sync smart shopping, Clear expired rows |
| **Shopping list todo** | Bidirectional sync — add, remove, check off items directly from HA |
| **Expiry calendar** | Every product's expiry date as a native HA calendar event — works with the calendar card and any calendar automation |
| **Quick-add text entity** | Type a product name in HA to instantly add it to the shopping list (great for voice assistants / Assist) |
| **6 services** | `add_to_shopping`, `mark_used`, `refresh`, `suggest_recipe`, `refresh_prices`, `clear_expired` |
| **Auto-discovery** | Detected automatically via Zeroconf/mDNS when `avahi-daemon` runs on the EverShelf host |
| **5 languages** | English, Italian, German, French, Spanish |
> **Requires a self-hosted EverShelf instance.** The integration talks directly to your server — no cloud involved.
> Full documentation: [ha-evershelf on GitHub](https://github.com/dadaloop82/ha-evershelf)
> L'IA est optionnelle. EverShelf fonctionne sans clé Gemini.
---
### 📦 Inventory Management
- **Export inventory** — Download the full inventory as a UTF-8 CSV (Excel-compatible) or open a print-ready page to save as PDF; export button always visible in the inventory page header
- **Barcode scanning** — Scan products with your phone camera using QuaggaJS; last 20 scanned products saved as tappable chips so you can re-select them without rescanning
- **AI identification** — Take a photo and let Google Gemini identify the product, with suggestions from your existing inventory; gracefully shows a friendly message when AI quota is exhausted instead of a raw API error
- **Smart locations** — Track items across Pantry, Fridge, Freezer, and custom locations
- **Expiry tracking** — Automatic shelf-life estimation based on product type and storage
- **Opened product tracking** — Reduced shelf-life calculation when packages are opened; opened-product expiry is now also checked when building banner alerts (not just the dashboard section)
- **Vacuum-sealed support** — Extended expiry dates for vacuum-sealed items; products sealed under vacuum are only flagged as expired after a configurable grace period past the printed date (`VACUUM_EXPIRY_EXTENSION_DAYS`, default 30 days, configurable in `.env`)
- **Anomaly detection** — Banner alerts for suspicious quantities and consumption predictions with inline correction; dismiss button now shows the current inventory quantity so the action is unambiguous ("Quantity is correct (2 pcs)")
## 🛒 Liste de courses intelligente
### 🤖 AI-Powered (Google Gemini)
- **Expiry date reading** — Photograph a label and extract the expiry date automatically
- **Product identification** — Point your camera at any product for instant recognition
- **Existing product matching** — AI scan shows matching products already in your pantry before suggesting new ones
- **Storage & shelf-life hint** — When adding a new product, Gemini suggests the optimal storage location and shelf-life in the background; shown as an inline AI badge next to the expiry estimate
- **Recipe generation** — Get personalized recipes based on what's in your pantry; streams live via Server-Sent Events so results appear as they are generated
- **Recipe stock hints** — Each pantry ingredient shows how much you have and what remains after use; when the leftover would be less than 5% of the full sealed package (10% for an already-opened partial pack), the recipe automatically uses everything on hand to avoid waste
- **Smart chat assistant** — Ask questions about your inventory, get cooking tips
- **Shopping suggestions with tips** — AI-powered purchase recommendations, each enriched with a short practical buying/storing tip
- **Anomaly explanation** — "Explain" button on anomaly banners explains in plain language why a discrepancy likely occurred and what to do
- **Model fallback** — All AI endpoints try `gemini-2.5-flash` first and fall back to `gemini-2.0-flash` automatically
- **Graceful no-key state** — When no Gemini key is configured, AI entry points show a friendly message; the header button is visually greyed with an amber dot
### 🛒 Shopping List
- **Bring! integration** — Sync with the [Bring!](https://www.getbring.com/) shopping list app
- **Generic shopping names** — Products are grouped by type (e.g. "Milk", "Cold cuts", "Cooking cream") rather than brand, keeping the Bring! list clean and consolidated
- **Smart predictions** — Know what you'll need before you run out
- **Auto-add on depletion** — When a product reaches zero the app adds it to Bring! automatically, no confirmation needed
- **Auto-remove on scan** — Products are removed from the shopping list when scanned in - **Auto-migration** — Items already on the Bring! list are silently renamed to their generic name in the background (throttled, runs on list load)
- **Catalog coverage** — All product types resolve to a German Bring! catalog key for icon and category display in the Bring! app
### 🍳 Cooking Mode
- **♻️ Zero-waste tips** — For each cooking step that generates reusable scraps (peels, cooking water, egg whites, cheese rinds, bread crusts, vegetable tops, etc.), a dismissible ♻️ tip card appears with a practical reuse idea; tips are generated by Gemini as part of the recipe at no extra API cost; opt-in toggle in Settings (default OFF)
- **Step-by-step guidance** — Follow recipes with a hands-free cooking interface
- **Text-to-Speech** — Voice readout of recipe steps; supports browser Web Speech API, native Android TTS (kiosk), or a custom REST endpoint (Home Assistant, etc.); retries voice loading for up to 10 seconds with a fallback refresh button; TTS activates automatically without requiring the global TTS setting to be enabled
- **Auto-read on navigate** — Each step is read aloud automatically when you tap Next or Previous; the first step is read when entering cooking mode
- **Timer voice alerts** — 10-second countdown warning spoken aloud before each timer expires; expiry announced vocally when time is up
- **Recipe completion** — "Bon appétit!" announced via TTS when the last step is confirmed
- **Built-in timer** — Automatic timer suggestions based on recipe instructions
- **Ingredient tracking** — Mark ingredients as used during cooking; leftover quantities prompt a "move to another location" flow
### 📊 Dashboard
- **Waste tracking** — Monitor consumed vs. wasted products over 30 days
- **Anti-waste report** — Personalised waste rate vs. national average with annual kg estimate; shown above the expiring-items list
- **Expiry alerts** — Visual warnings for expired and soon-to-expire items
- **Opened products panel** — Tracks partially-used items; expiry is recalculated from the opening date using AI (Gemini) + per-category rule fallback; whole sealed packages always keep their original manufacturer expiry; conf items with mixed whole + fractional units are shown as two separate entries
- **Freezer shelf-life** — Granular per-product estimates (USDA/EFSA): fish 120 d, poultry 270 d, whole red-meat cuts 365 d, mince 120 d, vegetables/fruit 270 d, generic 180 d; AI + cache still take priority over rules
- **Safety ratings** — Smart assessment of expired product safety (by category and location); expired unsafe items shown with a red danger banner and a discard action as the primary action
- **Expired product banner** — Products that have passed their effective shelf-life (including opened-product reduced expiry) appear in the top notification banner; icon, colour and title adapt to the actual safety level (✅ green for safe, 👀 amber to check, 🚫 red for danger); high-risk items get a prominent discard action
- **Quick recipe bar** — One-tap recipe suggestion using expiring products
- **Anomaly banner** — Scrollable banner with suspicious quantities and consumption prediction mismatches, with one-tap correction or inline edit
- **Expired/expiring alerts** — Priority-sorted banner notifications for expired and soon-to-expire products with use, throw, edit, and dismiss actions
- **Swipe navigation** — Touch swipe or tap arrows/dots to browse banner notifications
- **Quick-access buttons** — Recently used and most popular products shown on the inventory page for fast access
### 🌙 Appearance
- **Dark mode** — Three modes: Light, Dark, and Auto (time-based: dark from 20:00 to 07:00, light otherwise); applies immediately without page reload; auto mode re-evaluates every 5 minutes, so night/day transitions happen automatically even on always-on kiosk displays; theme is applied before the first render to prevent a white flash
- **Global settings tab** — A dedicated **⚙️ General** tab groups all system-wide settings (language, currency, theme, screensaver, zero-waste tips, export) at the top of the Settings panel
### Database Maintenance
- **Automatic cleanup** — Recipes older than `RECIPE_RETENTION_DAYS` (default 7) and transactions older than `TRANSACTION_RETENTION_DAYS` (default 7) are deleted automatically on every cron cycle; SQLite `VACUUM` runs after each cleanup to keep the file compact
- **Manual cleanup** — Trigger immediately via `GET /api/?action=db_cleanup`
- **Compact by default** — Fresh installs stay small; large accumulated databases shrink back to a few hundred KB within one cron cycle
### 📱 Progressive Web App
- **Mobile-first design** — Optimized for phones, works on tablets and desktop
- **Installable** — Add to home screen for a native app experience
- **Multi-device** — All user data (shopping tags, pinned items, location preferences, scan history) is stored server-side in SQLite and shared across every device on the same instance; no data is siloed in a single browser's localStorage
### 📶 Offline Mode
- **Automatic detection** — Full-screen overlay appears immediately on network loss; shows a "Continue offline" button after 3 s, and auto-enters offline mode after 8 s
- **Local inventory cache** — Inventory is synced to `localStorage` at every startup and on each successful API call; the offline view always reflects the last known state
- **Write queue** — Add, use, update and delete operations performed while offline are queued locally and synced to the server automatically on reconnect (including after a page refresh)
- **Optimistic UI** — Queued writes are applied immediately to the local cache so the interface stays responsive
- **Offline-computed stats** — Expiring and expired items are derived client-side from the cache; dashboard stat cards show real counts instead of zeros
- **AI/network sections hidden** — Anti-waste chart, nutrition analysis, recipe generator, price fetching, and Gemini chat are hidden in offline mode; the inventory, history, and manually-managed shopping list remain fully functional
- **Broken image fallback** — External product images (Open Food Facts, etc.) that fail to load are replaced with a neutral grey placeholder, keeping the layout intact
- **Startup recovery** — If the page is refreshed while operations are queued, they are detected and synced automatically on the next successful startup
- **Buffered error reporting** — `remoteLog` and `reportError` calls made while offline are stored locally and flushed to the server (and to GitHub issues) when the connection is restored
### ⚖️ Smart Scale Integration (Add-on)
- **Bluetooth gateway** — Connects a BLE smart scale to EverShelf via local WebSocket
- **SSE relay** — Server-side relay avoids mixed-content (HTTPS→WS) issues
- **Auto-discovery** — Server scans LAN to find the gateway automatically
- **Auto weight reading** — When adding/using a product with unit g/ml, weight fills automatically
- **10g threshold** — Ignores readings that haven't changed enough between products - **Duplicate-reading prevention** — Server-side 12-second dedup window rejects a second scale-triggered deduction of the same product, guarding against BLE multi-fire- **ml conversion hint** — Shows "weight in grams → will be converted to ml" when product unit is ml
- **Stability + auto-confirm** — 10s stable wait + 5s countdown before confirming
- **Real-time status** — Scale connection indicator always visible in the header
- **Multi-protocol** — Supports Bluetooth SIG Weight Scale, Body Composition, Xiaomi Mi Scale 2 and 100+ models
- **Built into kiosk (v1.6.0+)** — BLE gateway runs as an integrated foreground service inside the [EverShelf Kiosk](evershelf-kiosk/) app; no separate APK needed.
### 📺 Android Kiosk Mode (Add-on)
- **Dedicated tablet app** — Full-screen WebView wrapper for wall-mounted kitchen tablets
- **True kiosk lock** — Screen pinning blocks home/recent buttons
- **Setup wizard** — 6-step guided configuration (language, welcome, permissions, server URL, BLE scale scan, screensaver, summary)
- **Smart auto-discovery** — Scans the LAN in parallel (60 threads, TCP pre-check, ports 80/443/8080/8443) with real-time UI feedback; correctly identifies the device's Wi-Fi/Ethernet subnet (VPN and cellular interfaces are filtered out)
- **Built-in BLE scale gateway** — `GatewayService` foreground service; BLE scanning + WebSocket server `:8765` run directly inside the kiosk app. Select your scale in step 5 of the wizard — no external app required
- **Scale auto-configuration** — After selecting the BLE device, the wizard writes `scale_enabled` and `scale_gateway_url=ws://127.0.0.1:8765` to the server automatically
- **Camera & mic permissions** — Full hardware access for barcode scanning and voice; grant button transforms to a green confirmation after granting
- **Native TTS bridge** — Cooking mode voice readout uses the Android TextToSpeech engine directly, bypassing Web Speech API voice limitations; no offline voice packs required
- **Hard refresh** — ↻ button clears WebView cache to pick up web app updates
- **Update notifications** — Checks GitHub releases every 6h, shows banner when updates available
- **SSL support** — Accepts self-signed certificates
- **Android kiosk app** — [`evershelf-kiosk/`](evershelf-kiosk/) — downloadable APK
- Création automatique depuis les ruptures de stock
- Prévisions de besoins
- Synchronisation avec Bring!
- Nettoyage automatique des doublons
- Suggestions d'achat personnalisées
---
## 🚀 Quick Start
## 🍳 Mode cuisine
### Prerequisites
- **Web server** with PHP 8.0+ (Apache or Nginx)
- **PHP extensions**: `pdo_sqlite`, `curl`, `mbstring`, `json`
- **HTTPS** recommended (required for camera access on mobile)
- Recettes étape par étape
- Mode mains libres
- Synthèse vocale (TTS)
- Minuteurs automatiques
- Suivi des ingrédients utilisés
- Conseils anti-gaspillage
### Installation
---
#### Option A: Docker (recommended)
## ♻️ Réduction du gaspillage
- Suivi des aliments consommés ou jetés
- Analyse des pertes
- Alertes de péremption
- Suggestions pour utiliser les produits bientôt périmés
---
## 🏡 Intégrations
### Home Assistant
Intégration native disponible :
- Capteurs de stock
- Dates de péremption
- Liste de courses
- Calendrier des produits
- Actions personnalisées
- Suggestions de recettes IA
Compatible avec une installation 100% locale.
---
## 📱 Application mobile / PWA
- Interface adaptée smartphone
- Installation comme une application
- Synchronisation multi-appareils
- Mode hors-ligne :
- consultation du stock
- actions mises en attente
- synchronisation automatique au retour réseau
---
## 📺 Mode tablette (Kiosque Android)
- Affichage plein écran
- Verrouillage kiosque
- Scan caméra
- Support TTS natif Android
- Découverte automatique du serveur
- Support des balances Bluetooth
---
## 🚀 Installation rapide
### Prérequis
- PHP 8.0+
- SQLite 3
- Extensions PHP :
- `pdo_sqlite`
- `curl`
- `mbstring`
- `json`
Docker est recommandé.
---
### 🐳 Installation Docker (CLI)
```bash
# 1. Clone the repository
git clone https://github.com/dadaloop82/EverShelf.git
git clone https://git.mashome.fr/morgane/EverShelf.git
cd EverShelf
# 2. Create configuration file
cp .env.example .env
nano .env
# 3. Start with Docker Compose
docker compose up -d
# → Open http://localhost:8080
```
#### Option B: Manual
Puis ouvrez :
http://localhost:8080
```bash
# 1. Clone the repository
git clone https://github.com/dadaloop82/EverShelf.git
cd EverShelf
---
### 🐳 Déploiement via Portainer
# 2. Create configuration file
cp .env.example .env
1. Dans Portainer, va dans **Stacks****Add stack**
2. Donne un nom à la stack (ex : `evershelf`)
3. Colle le contenu de ton `docker-compose.yml` dans l'éditeur web (ou utilise l'option **Repository** en pointant vers `https://git.mashome.fr/morgane/EverShelf.git` et le chemin du fichier compose)
4. Renseigne tes variables d'environnement dans la section **Environment variables** (ou via un fichier `.env` à la racine du repo)
5. Clique sur **Deploy the stack**
# 3. Set permissions
chmod 755 data/
chmod 664 data/.gitkeep
chown -R www-data:www-data data/
Pour mettre à jour après une modification de code :
- Va dans **Stacks** → ta stack → **Update the stack**
- Coche bien **« Re-pull image »** / **« Re-build image »** avant de valider — sinon Portainer redémarre le conteneur avec l'image déjà construite en cache, sans prendre en compte tes changements.
# 4. Edit your configuration
nano .env
```
---
### Configuration (.env)
## ⚙️ Configuration
```ini
# Required for AI features (get a key at https://aistudio.google.com/app/apikey)
GEMINI_API_KEY=your_api_key_here
Exemple de fichier `.env` :
# Optional: Bring! shopping list integration
BRING_EMAIL=your_email@example.com
BRING_PASSWORD=your_password
```env
# IA Google Gemini (optionnel)
GEMINI_API_KEY=votre_cle
# Optional: Text-to-Speech for cooking mode
TTS_URL=http://your-home-assistant:8123/api/events/tts_speak
TTS_TOKEN=your_long_lived_token
TTS_ENABLED=true
# Bring! (optionnel)
BRING_EMAIL=email@example.com
BRING_PASSWORD=motdepasse
# Optional: DB retention and cleanup (applied automatically each cron cycle)
RECIPE_RETENTION_DAYS=7 # delete recipe plans older than N days
TRANSACTION_RETENTION_DAYS=90 # delete stock transactions older than N days (min 30 enforced)
# Optional: Vacuum-sealed expiry grace period
VACUUM_EXPIRY_EXTENSION_DAYS=30 # extra days before vacuum-sealed items are flagged expired
# Optional: Gemini cost rates (USD per million tokens, for the Info tab cost estimate)
GEMINI_COST_25F_IN=0.15
GEMINI_COST_25F_OUT=0.60
GEMINI_COST_20F_IN=0.10
GEMINI_COST_20F_OUT=0.40
# Optional: Security — protect all API endpoints
# Set a strong random string; clients send it as X-API-Token header (or ?api_token= for HA)
# Sécurité API
API_TOKEN=
# Optional: Legacy alias for API_TOKEN (settings save only)
SETTINGS_TOKEN=
# Optional: Demo mode — block all write operations at the router level
DEMO_MODE=false
# Optional: Logging
# LOG_LEVEL sets the minimum severity written to disk (DEBUG / INFO / WARN / ERROR)
# DEBUG also logs every SQL query executed against the database
LOG_LEVEL=INFO
LOG_ROTATE_HOURS=24 # hours before opening a new log file (default: 24)
LOG_MAX_FILES=14 # maximum number of rotated files to keep (default: 14)
# Nettoyage automatique
RECIPE_RETENTION_DAYS=7
TRANSACTION_RETENTION_DAYS=90
```
### Web Server Configuration
<details>
<summary><strong>Apache (.htaccess)</strong></summary>
The app works out of the box with Apache if placed in the web root or a subdirectory. Make sure `mod_rewrite` is enabled and `AllowOverride All` is set.
```apache
<Directory /var/www/html/evershelf>
AllowOverride All
Require all granted
</Directory>
```
</details>
<details>
<summary><strong>Nginx</strong></summary>
```nginx
server {
listen 80;
server_name your-server.local;
root /var/www/html/evershelf;
index index.html;
location /api/ {
try_files $uri $uri/ =404;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
# Deny access to sensitive files
location ~ /\.env { deny all; }
location ~ /data/ { deny all; }
location ~ /backup\.sh { deny all; }
}
```
</details>
### HTTPS Setup (Recommended)
Camera access requires HTTPS on most mobile browsers. Options:
- **Let's Encrypt** with Certbot (for public-facing servers)
- **Self-signed certificate** (for local network only)
- **Reverse proxy** (e.g., Caddy, Traefik) with automatic TLS
### Cron Job (Optional)
Set up a cron job for smart shopping predictions:
```bash
# Run every 5 minutes
*/5 * * * * php /path/to/evershelf/api/cron_smart_shopping.php >> /path/to/evershelf/data/cron.log 2>&1
```
### Backup (Optional)
The included `backup.sh` creates local daily backups of your database:
```bash
# Run daily at 3 AM
0 3 * * * /path/to/evershelf/backup.sh
```
### Google Drive Backup (Optional)
EverShelf supports automatic daily backups to Google Drive via OAuth 2.0. This works on any server, including private IP / local network setups (no public domain required).
**Setup:**
1. Go to [console.cloud.google.com](https://console.cloud.google.com) and select or create a project.
2. Enable the **Google Drive API** (`APIs & Services → Enable APIs → Google Drive API`).
3. Go to `APIs & Services → Credentials → Create Credentials → OAuth client ID`.
4. Application type: **Web application**.
5. Add **`http://localhost`** as an Authorized Redirect URI (this is the key — it works even without a real domain).
6. Copy **Client ID** and **Client Secret** into EverShelf Settings → Backup.
7. Enter your **Google Drive Folder ID** (the last part of the folder URL).
8. Click **Authorize with Google** and sign in.
9. The browser will redirect to `http://localhost` and may show a connection error — **this is expected**. Copy the full URL from the address bar (e.g. `http://localhost/?code=4%2F0A...`) and paste it into the field that appears in EverShelf, then click **Submit**.
> **Note:** While the OAuth app is in *Testing* status in Google Cloud Console, you must add your Google account as a test user under `APIs & Services → OAuth consent screen → Test users`.
---
## 🏗️ Architecture
## 🔒 Vie privée
```
evershelf/
├── index.html # Single-page application (SPA)
├── manifest.json # PWA manifest
├── .env.example # Configuration template
├── backup.sh # Local database backup script
├── LICENSE # MIT License
├── api/
│ ├── index.php # Main API router (all endpoints)
│ ├── database.php # SQLite schema, migrations, helpers
│ └── cron_smart_shopping.php # Background job for predictions
├── assets/
│ ├── css/style.css # All application styles
│ ├── js/app.js # All application logic
│ └── img/ # Static images
└── data/ # Runtime data (gitignored)
├── evershelf.db # SQLite database (auto-created)
├── backups/ # Local DB backups
└── *.json # Token/cache files
EverShelf est conçu pour fonctionner en auto-hébergement :
evershelf-scale-gateway/ # ⚖️ Android BLE gateway [DEPRECATED — integrated into kiosk v1.6.0+]
├── README.md # Deprecation notice + legacy docs
└── app/src/ # Kotlin Android source (WebSocket + BLE)
evershelf-kiosk/ # 📺 Android kiosk app (add-on)
├── README.md # Setup & feature docs
└── app/src/ # Kotlin Android source (WebView wrapper)
```
### API Endpoints
| Category | Action | Method | Description |
|----------|--------|--------|-------------|
| **Products** | `search_barcode` | GET | Find product by barcode |
| | `lookup_barcode` | GET | Look up barcode on Open Food Facts |
| | `product_save` | POST | Create or update a product |
| | `products_list` | GET | List all products |
| **Inventory** | `inventory_list` | GET | List inventory items |
| | `inventory_add` | POST | Add product to inventory |
| | `inventory_use` | POST | Use/consume from inventory |
| | `inventory_summary` | GET | Count by location |
| **AI** | `gemini_identify` | POST | Identify product from photo |
| | `gemini_expiry` | POST | Read expiry date from photo |
| | `gemini_chat` | POST | Chat with AI assistant |
| | `generate_recipe` | POST | Generate recipe from inventory |
| | `gemini_product_hint` | POST | Storage location + shelf-life hint |
| | `gemini_shopping_enrich` | POST | Enrich shopping suggestions with tips |
| | `gemini_anomaly_explain` | POST | Plain-language anomaly explanation |
| **Shopping** | `bring_list` | GET | Get Bring! shopping list |
| | `bring_add` | POST | Add items to Bring! |
| | `smart_shopping` | GET | Smart shopping predictions |
| **Settings** | `get_settings` | GET | Get server configuration |
| | `save_settings` | POST | Update server configuration |
- Pas de compte obligatoire
- Pas de cloud imposé
- Données stockées localement
- SQLite comme base de données
- Les fonctions IA utilisent uniquement les services configurés par l'utilisateur
---
## 🔒 Security Notes
## 🛠️ Développement
- **Credentials** are stored in `.env` (server-side, never committed to Git)
- **Database** stays local — never pushed to remote repositories
- **Apache/Nginx hardening** — `.env`, `data/`, and `logs/` are blocked from direct HTTP access
- **API token** — set `API_TOKEN` in `.env` to require `X-API-Token` on all API calls (Home Assistant: `?api_token=`)
- **API keys are never exposed to the browser** — `get_settings` returns only boolean flags (`gemini_key_set`, `ha_token_set`, …)
- **GitHub Issues token** — stored encrypted as `GH_ISSUE_TOKEN_ENC` + `GH_ISSUE_TOKEN_KEY` (see `scripts/encrypt-gh-token.php`)
- **Settings write protection** — `save_settings` requires the same API token when configured; validated with `hash_equals`
- **Demo / public mode** — set `DEMO_MODE=true` to block all write operations at the PHP router level before any business logic runs
- The API uses **parameterized SQL queries** (PDO prepared statements) against injection
- **Input validation** on all inventory operations (quantity bounds, location whitelist)
- Consider adding **reverse-proxy authentication** (e.g. Authelia, Nginx `auth_basic`) if the server is accessible from the internet
Technologies principales :
- PHP
- SQLite
- JavaScript
- HTML/CSS
- Docker
---
## 🛠️ Development
## 📜 Licence
```bash
# Run PHP's built-in server for local development
php -S localhost:8080 -t /path/to/evershelf
# Check PHP syntax
php -l api/index.php
php -l api/database.php
```
The application uses no build tools — edit files directly and refresh.
Projet sous licence MIT.
---
## 📋 Roadmap
## 🙏 Crédits
Feature requests, bug reports and planned work are tracked in the [**EverShelf Roadmap**](https://github.com/users/dadaloop82/projects/2) GitHub Project.
Ce fork, **EverShelf for Ricardo**, est maintenu par Morgane pour servir de système de gestion de stock/recettes à l'application **Ricardo**.
---
Projet original :
## 🌐 Translations
https://github.com/dadaloop82/EverShelf
The app supports multiple languages via JSON translation files in the `translations/` folder.
| Language | Status |
|----------|--------|
| 🇮🇹 Italian (it) | ✅ Complete (base) |
| 🇬🇧 English (en) | ✅ Complete |
| 🇩🇪 German (de) | ✅ Complete |
| 🇫🇷 French (fr) | ✅ Complete |
| 🇪🇸 Spanish (es) | ✅ Complete |
**Want to add your language?** See the [Translation Guide](CONTRIBUTING.md#-adding-translations) — just copy `translations/it.json`, translate the values, and submit a PR!
---
## 🤝 Contributing
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/my-feature`)
3. Commit your changes (`git commit -m 'Add my feature'`)
4. Push to the branch (`git push origin feature/my-feature`)
5. Open a Pull Request
### Easiest way to start — translate EverShelf into your language
Translations are just JSON files. No coding, no setup — fork → edit → PR.
```
translations/
├── it.json ✅ Italian (base)
├── en.json ✅ English
├── de.json ✅ German
├── fr.json ✅ French
├── es.json ✅ Spanish
├── pt.json ❌ Portuguese — wanted!
├── nl.json ❌ Dutch — wanted!
└── ... ❌ Your language here!
```
👉 See [issue #93](https://github.com/dadaloop82/EverShelf/issues/93) to claim a language.
### Other ways to contribute
| What | Skill needed |
|---|---|
| 🐛 Report a bug | None |
| 📖 Improve the wiki | Markdown |
| 🌍 Add a translation | JSON editing |
| 🎨 Fix a CSS/UI issue | CSS / HTML |
| ⚙️ Implement a feature | PHP / JS |
| ⭐ Star the repo | Clicking |
👉 Browse [`help wanted`](https://github.com/dadaloop82/EverShelf/labels/help%20wanted) issues for good starting points.
Read [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide (branch naming, code style, how to run locally).
---
## 💬 Community
Join the conversation in [GitHub Discussions](https://github.com/dadaloop82/EverShelf/discussions):
- **Vote on upcoming features** — tell us what to build next
- **Show your setup** — share your kitchen kiosk
- **Ask questions** — get help from the community
---
## 📄 License
This project is licensed under the **MIT License** — see the [LICENSE](LICENSE) file for details.
---
## 👨‍💻 Author
**Stimpfl Daniel** — [evershelfproject@gmail.com](mailto:evershelfproject@gmail.com)
- Website: [evershelfproject.dadaloop.it](https://evershelfproject.dadaloop.it/)
- GitHub: [@dadaloop82](https://github.com/dadaloop82)
---
## 📸 Screenshots
<div align="center">
![EverShelf demo — barcode scan, inventory management and AI recipe generation](assets/img/demo.gif)
</div>
For a live walkthrough with real data and full AI enabled, visit the **[live demo](https://evershelfproject.dadaloop.it/demo)** — no installation required.
> Want to contribute additional screenshots? See [CONTRIBUTING.md](CONTRIBUTING.md) — PRs welcome!
Ce dépôt contient des améliorations et adaptations personnelles, incluant un système d'export/import avec fusion intelligente des données.
+5
View File
@@ -2,6 +2,11 @@
/**
* EverShelf API bootstrap — shared by HTTP router and cron.
*/
// Never emit HTML notices before JSON API responses (breaks fetch().json() in the PWA).
if (!defined('CRON_MODE') && (getenv('DISPLAY_ERRORS') ?: '') !== '1') {
ini_set('display_errors', '0');
ini_set('html_errors', '0');
}
require_once __DIR__ . '/lib/env.php';
require_once __DIR__ . '/lib/constants.php';
require_once __DIR__ . '/lib/github.php';
+273 -3
View File
@@ -38,8 +38,24 @@ function _ensureDataDir(): void {
}
}
/** Ensure the SQLite DB and WAL sidecar files are writable (Docker volume first-boot). */
function _ensureDbWritable(): void {
if (!file_exists(DB_PATH)) {
return;
}
if (!is_writable(DB_PATH)) {
@chmod(DB_PATH, 0664);
}
foreach ([DB_PATH . '-wal', DB_PATH . '-shm'] as $sidecar) {
if (file_exists($sidecar) && !is_writable($sidecar)) {
@chmod($sidecar, 0664);
}
}
}
function getDB(): PDO {
_ensureDataDir();
_ensureDbWritable();
// logger.php is required by index.php before getDB() is called.
// In cron context it may not be loaded yet — guard with class_exists.
$useLogging = class_exists('LoggingPDO', false);
@@ -53,7 +69,7 @@ function getDB(): PDO {
$db->setAttribute(PDO::ATTR_TIMEOUT, 5); // PDO::ATTR_TIMEOUT is in seconds for MySQL, but not directly for SQLite.
// For SQLite, we use PRAGMA busy_timeout.
$db->exec('PRAGMA journal_mode = WAL;');
$db->exec('PRAGMA busy_timeout = 5000;'); // 5000 milliseconds = 5 seconds
$db->exec('PRAGMA busy_timeout = 10000;'); // 10 s — cron + PWA writes can contend under WAL
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec("PRAGMA journal_mode=WAL");
@@ -72,6 +88,29 @@ function getDB(): PDO {
return $db;
}
/**
* Retry a DB write when SQLite returns "database is locked" (concurrent cron + API).
*
* @template T
* @param callable(): T $fn
* @return T
*/
function dbWithRetry(callable $fn, int $maxAttempts = 4): mixed {
$attempt = 0;
while (true) {
try {
return $fn();
} catch (\PDOException $e) {
$attempt++;
$locked = str_contains($e->getMessage(), 'database is locked');
if (!$locked || $attempt >= $maxAttempts) {
throw $e;
}
usleep(150000 * $attempt); // 150 ms, 300 ms, 450 ms …
}
}
}
function initializeDB(PDO $db): void {
$db->exec("
CREATE TABLE IF NOT EXISTS products (
@@ -147,7 +186,11 @@ function migrateDB(PDO $db): void {
try { $db->exec("ALTER TABLE products ADD COLUMN shopping_name TEXT DEFAULT ''"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
if (!in_array('subcategory', $colNames)) {
try { $db->exec("ALTER TABLE products ADD COLUMN subcategory TEXT DEFAULT NULL"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
// Empty barcode strings break UNIQUE (only one '' allowed); normalize to NULL.
$db->exec("UPDATE products SET barcode = NULL WHERE barcode IS NOT NULL AND TRIM(barcode) = ''");
@@ -280,6 +323,227 @@ function migrateDB(PDO $db): void {
$db->exec("CREATE INDEX IF NOT EXISTS idx_transactions_type_date ON transactions(type, created_at)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_transactions_pid_type_undone ON transactions(product_id, type, undone)");
// Custom locations table (v1.9.0) — dynamic inventory locations managed from Settings
$locTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='locations'")->fetchAll();
if (empty($locTables)) {
$db->exec("
CREATE TABLE locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
label TEXT NOT NULL,
icon TEXT DEFAULT '📦',
sort_order INTEGER DEFAULT 0,
is_builtin INTEGER DEFAULT 0
);
");
$db->exec("INSERT INTO locations (key, label, icon, sort_order, is_builtin) VALUES
('dispensa', 'Dispensa', '🗄️', 1, 1),
('frigo', 'Frigo', '🧊', 2, 1),
('freezer', 'Freezer', '❄️', 3, 1),
('altro', 'Altro', '📦', 4, 1)
");
}
// Custom subcategories table (v2.0) — sous-catégories par catégorie, gérables depuis Config
$subcatTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='subcategories'")->fetchAll();
if (empty($subcatTables)) {
$db->exec("
CREATE TABLE subcategories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
key TEXT NOT NULL,
label TEXT NOT NULL,
sort_order INTEGER DEFAULT 0,
UNIQUE(category, key)
);
");
$db->exec("INSERT INTO subcategories (category, key, label, sort_order) VALUES
('latticini', 'lait', '🥛 Lait', 1),
('latticini', 'yaourt', '🥣 Yaourt', 2),
('latticini', 'fromage', '🧀 Fromage', 3),
('latticini', 'beurre', '🧈 Beurre', 4),
('latticini', 'creme', '🍦 Crème', 5),
('latticini', 'oeufs', '🥚 Œufs', 6),
('latticini', 'autre', '📦 Autre', 7),
('carne', 'poulet', '🍗 Poulet', 1),
('carne', 'boeuf', '🐄 Bœuf', 2),
('carne', 'porc', '🐖 Porc', 3),
('carne', 'agneau', '🐑 Agneau', 4),
('carne', 'charcuterie', '🥓 Charcuterie', 5),
('carne', 'autre', '📦 Autre', 6),
('pesce', 'poisson_frais', '🐟 Poisson frais', 1),
('pesce', 'poisson_surgele', '🧊 Poisson surgelé', 2),
('pesce', 'fruits_mer', '🦐 Fruits de mer', 3),
('pesce', 'conserve_poisson', '🥫 Conserve', 4),
('pesce', 'autre', '📦 Autre', 5),
('frutta', 'agrumes', '🍊 Agrumes', 1),
('frutta', 'baies', '🫐 Baies', 2),
('frutta', 'fruits_noyau', '🍑 Fruits à noyau', 3),
('frutta', 'fruits_tropicaux', '🍍 Fruits tropicaux', 4),
('frutta', 'autre', '📦 Autre', 5),
('verdura', 'legumes_feuilles', '🥬 Légumes feuilles', 1),
('verdura', 'legumes_racines', '🥕 Légumes racines', 2),
('verdura', 'legumineuses_fraiches', '🌱 Légumineuses fraîches', 3),
('verdura', 'autre', '📦 Autre', 4),
('pasta', 'pates', '🍝 Pâtes', 1),
('pasta', 'riz', '🍚 Riz', 2),
('pasta', 'semoule', '🌾 Semoule', 3),
('pasta', 'autre', '📦 Autre', 4),
('pane', 'pain_frais', '🍞 Pain frais', 1),
('pane', 'biscottes', '🥖 Biscottes', 2),
('pane', 'viennoiserie', '🥐 Viennoiserie', 3),
('pane', 'autre', '📦 Autre', 4),
('surgelati', 'plats_prepares', '🍱 Plats préparés', 1),
('surgelati', 'legumes_surgeles', '🧊 Légumes surgelés', 2),
('surgelati', 'glaces', '🍨 Glaces', 3),
('surgelati', 'viande_poisson_surgele', '🧊 Viande/poisson surgelé', 4),
('surgelati', 'autre', '📦 Autre', 5),
('bevande', 'vin', '🍷 Vin', 1),
('bevande', 'biere', '🍺 Bière', 2),
('bevande', 'spiritueux', '🥃 Spiritueux', 3),
('bevande', 'soda', '🥤 Soda', 4),
('bevande', 'jus', '🧃 Jus', 5),
('bevande', 'eau', '💧 Eau', 6),
('bevande', 'autre', '📦 Autre', 7),
('condimenti', 'huile', '🫒 Huile', 1),
('condimenti', 'vinaigre', '🍶 Vinaigre', 2),
('condimenti', 'sauce', '🥫 Sauce', 3),
('condimenti', 'epice', '🌿 Épice', 4),
('condimenti', 'autre', '📦 Autre', 5),
('snack', 'chocolat', '🍫 Chocolat', 1),
('snack', 'biscuit', '🍪 Biscuit', 2),
('snack', 'chips', '🥔 Chips', 3),
('snack', 'bonbon', '🍬 Bonbon', 4),
('snack', 'autre', '📦 Autre', 5),
('conserve', 'legumes_conserve', '🥫 Légumes', 1),
('conserve', 'fruits_conserve', '🥫 Fruits', 2),
('conserve', 'poisson_conserve', '🥫 Poisson', 3),
('conserve', 'confiture', '🍯 Confiture', 4),
('conserve', 'autre', '📦 Autre', 5),
('cereali', 'cereales_petitdej', '🥣 Céréales petit-déj', 1),
('cereali', 'legumineuses_seches', '🫘 Légumineuses sèches', 2),
('cereali', 'farine', '🌾 Farine', 3),
('cereali', 'autre', '📦 Autre', 4)
");
}
// Custom categories table (v2.1) — catégories produit, gérables depuis Config
$catTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='categories'")->fetchAll();
if (empty($catTables)) {
$db->exec("
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
label TEXT NOT NULL,
icon TEXT DEFAULT '📦',
keywords TEXT DEFAULT '',
sort_order INTEGER DEFAULT 0,
is_builtin INTEGER DEFAULT 0
);
");
$db->exec("INSERT INTO categories (key, label, icon, sort_order, is_builtin) VALUES
('latticini', 'Latticini', '🥛', 1, 1),
('carne', 'Carne', '🥩', 2, 1),
('pesce', 'Pesce', '🐟', 3, 1),
('frutta', 'Frutta', '🍎', 4, 1),
('verdura', 'Verdura', '🥬', 5, 1),
('pasta', 'Pasta', '🍝', 6, 1),
('pane', 'Pane', '🍞', 7, 1),
('surgelati', 'Surgelati', '🧊', 8, 1),
('bevande', 'Bevande', '🥤', 9, 1),
('condimenti', 'Condimenti', '🧂', 10, 1),
('snack', 'Snack', '🍪', 11, 1),
('conserve', 'Conserve', '🥫', 12, 1),
('cereali', 'Cereali', '🌾', 13, 1),
('igiene', 'Igiene', '🧴', 14, 1),
('pulizia', 'Pulizia', '🧹', 15, 1),
('altro', 'Altro', '📦', 16, 1)
");
}
// Recipe library (v2.2) — recettes ajoutées manuellement (cocktails, boissons...), distinctes du planning repas
$recipeLibTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='recipe_library'")->fetchAll();
if (empty($recipeLibTables)) {
$db->exec("
CREATE TABLE recipe_library (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
recipe_json TEXT NOT NULL,
is_favorite INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
");
}
// Recipe tags (v2.3) — tags pour trier/filtrer "Mes recettes", gérables depuis Config
$recipeTagsTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='recipe_tags'")->fetchAll();
if (empty($recipeTagsTables)) {
$db->exec("
CREATE TABLE recipe_tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
label TEXT NOT NULL,
icon TEXT DEFAULT '🏷️',
sort_order INTEGER DEFAULT 0
);
");
$db->exec("INSERT INTO recipe_tags (key, label, icon, sort_order) VALUES
('cocktail', 'Cocktail', '🍹', 1),
('sans_alcool', 'Sans alcool', '🚫', 2),
('shot', 'Shot', '🥃', 3),
('long_drink', 'Long drink', '🍺', 4),
('aperitif', 'Apéritif', '🍾', 5),
('digestif', 'Digestif', '❄️', 6),
('rhum', 'Rhum', '🥃', 7),
('vodka', 'Vodka', '🍸', 8),
('gin', 'Gin', '🌿', 9),
('whisky', 'Whisky', '🥃', 10),
('tequila', 'Tequila', '🌵', 11),
('vin', 'Vin', '🍷', 12),
('champagne', 'Champagne / Mousseux', '🍾', 13),
('acidule', 'Acidulé', '🍋', 14),
('sucre', 'Sucré', '🍬', 15),
('amer', 'Amer', '☕', 16),
('epice', 'Épicé', '🌶️', 17),
('fruite', 'Fruité', '🍊', 18),
('herbace', 'Herbacé', '🌿', 19),
('ete', 'Été', '☀️', 20),
('hiver', 'Hiver', '❄️', 21),
('soiree', 'Soirée', '🎉', 22),
('brunch', 'Brunch', '🥂', 23)
");
}
// Migration: add keywords column to recipe_tags if missing
$rtCols = array_column($db->query("PRAGMA table_info(recipe_tags)")->fetchAll(), 'name');
if (!in_array('keywords', $rtCols)) {
try { $db->exec("ALTER TABLE recipe_tags ADD COLUMN keywords TEXT DEFAULT ''"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
// Custom quantity units (v2.4) — unités personnalisées (ex: kg, L) avec facteur de conversion vers pz/g/ml, gérables depuis Config
$customUnitsTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='custom_units'")->fetchAll();
if (empty($customUnitsTables)) {
$db->exec("
CREATE TABLE custom_units (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
label TEXT NOT NULL,
icon TEXT DEFAULT '📏',
base_unit TEXT NOT NULL DEFAULT 'g',
factor REAL NOT NULL DEFAULT 1,
sort_order INTEGER DEFAULT 0
);
");
}
// Add display_unit_key column to products if missing — unité personnalisée a afficher pour ce produit
$prodColsUnits = array_column($db->query("PRAGMA table_info(products)")->fetchAll(), 'name');
if (!in_array('display_unit_key', $prodColsUnits)) {
try { $db->exec("ALTER TABLE products ADD COLUMN display_unit_key TEXT DEFAULT NULL"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
// Internal shopping list table (v1.8.0) — used when SHOPPING_MODE=internal
// Internal shopping list table (v1.8.0) — used when SHOPPING_MODE=internal
$shopTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='shopping_list'")->fetchAll();
if (empty($shopTables)) {
@@ -309,6 +573,12 @@ function migrateDB(PDO $db): void {
try { $db->exec("ALTER TABLE products ADD COLUMN nutriments_json TEXT DEFAULT NULL"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
// Add tags column to products if missing (bugfix: insert referenced a column never created)
if (!in_array('tags', $prodCols2)) {
try { $db->exec("ALTER TABLE products ADD COLUMN tags TEXT DEFAULT NULL"); }
catch (PDOException $e) { if (strpos($e->getMessage(), 'duplicate column') === false) throw $e; }
}
}
/**
@@ -636,4 +906,4 @@ function getVacuumExpiryDaysPHP(int $baseDays): int {
if ($baseDays <= 30) return (int)round($baseDays * 2.5);
if ($baseDays <= 90) return (int)round($baseDays * 2.5);
return (int)round($baseDays * 1.5);
}
}
+2066 -334
View File
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -2,7 +2,6 @@
/**
* EverShelf — environment variable loader (.env).
*/
function loadEnv(): array {
static $cache = null;
if ($cache !== null) {
@@ -22,14 +21,20 @@ function loadEnv(): array {
}
return $cache;
}
function env(string $key, string $default = ''): string {
$vars = loadEnv();
return $vars[$key] ?? $default;
if (isset($vars[$key]) && $vars[$key] !== '') {
return $vars[$key];
}
// Fallback to system/Docker environment variables (e.g. set via Portainer)
$sysVal = getenv($key);
if ($sysVal !== false && $sysVal !== '') {
return $sysVal;
}
return $default;
}
/** Push a single key into the in-memory env cache (after .env write). */
function envCacheSet(string $key, string $value): void {
loadEnv();
// Force reload on next call — callers should use loadEnv() return for batch updates
}
}
+1
View File
@@ -335,6 +335,7 @@ class LoggingPDOStatement {
// Type hint: use PDO in all functions (LoggingPDO extends PDO).
// ═══════════════════════════════════════════════════════════════════════════
class LoggingPDO extends \PDO {
#[\ReturnTypeWillChange]
public function prepare(string $query, array $options = []): LoggingPDOStatement|false {
$stmt = parent::prepare($query, $options);
if ($stmt === false) {
+35
View File
@@ -1847,6 +1847,41 @@ body.server-offline .bottom-nav {
border-color: var(--primary);
}
.qty-control-with-unit {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.qty-control-with-unit .qty-control {
flex: 0 0 auto;
}
.qty-unit-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 52px;
height: 50px;
padding: 0 12px;
border-radius: var(--radius-sm);
background: var(--primary);
color: #fff;
font-size: 1rem;
font-weight: 800;
letter-spacing: 0.02em;
text-transform: lowercase;
white-space: nowrap;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}
.qty-unit-badge.qty-unit-muted {
background: var(--bg-card);
color: var(--primary);
border: 2px solid var(--primary);
}
/* ===== USE OPTIONS ===== */
.use-options {
display: flex;
+1891 -255
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@
# Retention follows BACKUP_RETENTION_DAYS from .env (default 3)
set -euo pipefail
INSTALL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
INSTALL_DIR="$(cd "$(dirname "$0")" && pwd)"
BACKUP_DIR="${INSTALL_DIR}/data/backups"
ENV_FILE="${INSTALL_DIR}/.env"
+9 -2
View File
@@ -5,14 +5,21 @@ services:
ports:
- "8080:80"
volumes:
# Persist database and runtime data
- evershelf_data:/var/www/html/data
# Mount your local .env configuration
- ./.env:/var/www/html/.env:ro
restart: unless-stopped
environment:
- TZ=Europe/Rome
networks:
- backend
- frontend
volumes:
evershelf_data:
driver: local
networks:
frontend:
external: true
backend:
external: true
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "it.dadaloop.evershelf.kiosk"
minSdk = 24
targetSdk = 35
versionCode = 18
versionName = "1.7.17"
versionCode = 20
versionName = "1.7.19"
}
signingConfigs {
@@ -643,6 +643,79 @@ class KioskActivity : AppCompatActivity() {
webView.evaluateJavascript("$jsCallback($escaped)", null)
}
}
val currentKiosk = try {
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
} catch (_: Exception) { "" }
val installedVc: Long = try {
val pi = packageManager.getPackageInfo(packageName, 0)
if (Build.VERSION.SDK_INT >= 28) pi.longVersionCode
else @Suppress("DEPRECATION") pi.versionCode.toLong()
} catch (_: Exception) { -1L }
fun semverNewer(remote: String, local: String): Boolean {
val r = remote.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val l = local.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
for (i in 0 until maxOf(r.size, l.size)) {
val rv = r.getOrElse(i) { 0 }
val lv = l.getOrElse(i) { 0 }
if (rv != lv) return rv > lv
}
return false
}
fun needsUpdate(remoteVersion: String, remoteVc: Long): Boolean = when {
remoteVc > 0 && installedVc >= 0 -> remoteVc > installedVc
currentKiosk.isNotEmpty() && remoteVersion.matches(Regex("\\d+\\.\\d+.*")) ->
semverNewer(remoteVersion, currentKiosk)
else -> false
}
fun applyUpdate(remoteVersion: String, apkUrl: String) {
val result = JSONObject()
.put("has_update", true)
.put("current", currentKiosk)
.put("latest", remoteVersion)
.put("apk_url", apkUrl)
notifyJs(result)
prefs.edit()
.putString(KEY_PENDING_UPDATE_VERSION, remoteVersion)
.putString(KEY_PENDING_UPDATE_URL, apkUrl)
.apply()
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $currentKiosk$remoteVersion", apkUrl) }
}
// 1) Prefer LAN/self-hosted update (no GitHub required)
val baseUrl = (prefs.getString(KEY_URL, "") ?: "").trim().trimEnd('/')
if (baseUrl.isNotEmpty()) {
try {
val localApi = "$baseUrl/api/index.php?action=kiosk_update"
val conn = openTrustedConnection(localApi)
conn.connectTimeout = 5000
conn.readTimeout = 5000
if (conn.responseCode == 200) {
val localJson = JSONObject(conn.inputStream.bufferedReader().readText())
conn.disconnect()
if (localJson.optBoolean("success")) {
val remoteVersion = localJson.optString("version", "")
val remoteVc = localJson.optLong("version_code", -1L)
val apkUrl = localJson.optString("apk_url", "")
if (apkUrl.isNotEmpty() && needsUpdate(remoteVersion, remoteVc)) {
applyUpdate(remoteVersion, apkUrl)
return@Thread
}
if (!needsUpdate(remoteVersion, remoteVc)) {
notifyJs(JSONObject().put("has_update", false).put("source", "local"))
prefs.edit().remove(KEY_PENDING_UPDATE_VERSION).remove(KEY_PENDING_UPDATE_URL).apply()
return@Thread
}
}
} else conn.disconnect()
} catch (_: Exception) { /* fall through to GitHub */ }
}
// 2) GitHub release fallback (requires internet)
try {
val conn = URL(GITHUB_RELEASES_API).openConnection() as java.net.HttpURLConnection
conn.setRequestProperty("Accept", "application/vnd.github+json")
@@ -657,43 +730,16 @@ class KioskActivity : AppCompatActivity() {
val body = conn.inputStream.bufferedReader().readText()
conn.disconnect()
val json = JSONObject(body)
val latestTag = json.optString("tag_name", "")
if (latestTag.isEmpty()) {
notifyJs(JSONObject().put("has_update", false).put("error", "no tag"))
return@Thread
}
val currentKiosk = try {
packageManager.getPackageInfo(packageName, 0).versionName ?: ""
} catch (_: Exception) { "" }
// The kiosk-latest release uses a non-semver tag ("kiosk-latest").
// Extract the actual kiosk version from the release body text.
// Body format: "Alias automatico → kiosk-X.Y.Z" or just "kiosk-X.Y.Z".
// Fall back to stripping the tag prefix if body parsing fails.
val bodyText = json.optString("body", "")
val norm = { v: String -> v.replace(Regex("^[^0-9]*"), "") }
val remoteKioskVersion = Regex("""kiosk-v?(\d+\.\d+(?:\.\d+)?)""")
.find(bodyText)?.groupValues?.get(1)
?.takeIf { it.isNotEmpty() }
?: norm(latestTag)
?: norm(json.optString("tag_name", ""))
// Compare semver: returns true if `remote` is strictly greater than `local`
fun semverNewer(remote: String, local: String): Boolean {
val r = remote.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val l = local.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val len = maxOf(r.size, l.size)
for (i in 0 until len) {
val rv = r.getOrElse(i) { 0 }
val lv = l.getOrElse(i) { 0 }
if (rv != lv) return rv > lv
}
return false
}
val remoteVc = Regex("""versionCode[=:\s(]+(\d+)""", RegexOption.IGNORE_CASE)
.find(bodyText)?.groupValues?.get(1)?.toLongOrNull() ?: -1L
val isSemver = remoteKioskVersion.matches(Regex("\\d+\\.\\d+.*"))
// Get APK URL from assets; fall back to the hardcoded KIOSK_DOWNLOAD_URL
val assets = json.optJSONArray("assets")
var kioskApkUrl = ""
if (assets != null) {
@@ -707,38 +753,35 @@ class KioskActivity : AppCompatActivity() {
}
if (kioskApkUrl.isEmpty()) kioskApkUrl = KIOSK_DOWNLOAD_URL
// Only flag an update when the remote version is parseable as semver AND
// strictly greater than the installed version.
val kioskNeedsUpdate = currentKiosk.isNotEmpty() && isSemver &&
semverNewer(remoteKioskVersion, currentKiosk)
val result = JSONObject()
.put("has_update", kioskNeedsUpdate)
.put("current", currentKiosk)
.put("latest", remoteKioskVersion)
.put("apk_url", kioskApkUrl)
notifyJs(result)
if (!kioskNeedsUpdate) {
// Clear any stale pending update if the current version is now up to date
if (!needsUpdate(remoteKioskVersion, remoteVc)) {
notifyJs(JSONObject().put("has_update", false))
prefs.edit().remove(KEY_PENDING_UPDATE_VERSION).remove(KEY_PENDING_UPDATE_URL).apply()
return@Thread
}
// Persist the pending update so the banner reappears after a crash/restart
prefs.edit()
.putString(KEY_PENDING_UPDATE_VERSION, remoteKioskVersion)
.putString(KEY_PENDING_UPDATE_URL, kioskApkUrl)
.apply()
runOnUiThread { showNativeUpdateBanner("🔄 Kiosk $currentKiosk$remoteKioskVersion", kioskApkUrl) }
applyUpdate(remoteKioskVersion, kioskApkUrl)
} catch (e: Exception) {
notifyJs(JSONObject().put("has_update", false).put("error", e.message ?: "network error"))
}
}.start()
}
/** HTTPS with self-signed cert support (LAN servers). */
private fun openTrustedConnection(urlStr: String): java.net.HttpURLConnection {
val conn = URL(urlStr).openConnection()
if (conn is javax.net.ssl.HttpsURLConnection) {
val trustAll = arrayOf<javax.net.ssl.TrustManager>(object : javax.net.ssl.X509TrustManager {
override fun checkClientTrusted(c: Array<java.security.cert.X509Certificate>?, t: String?) {}
override fun checkServerTrusted(c: Array<java.security.cert.X509Certificate>?, t: String?) {}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
})
val sc = javax.net.ssl.SSLContext.getInstance("TLS")
sc.init(null, trustAll, java.security.SecureRandom())
conn.sslSocketFactory = sc.socketFactory
conn.hostnameVerifier = javax.net.ssl.HostnameVerifier { _, _ -> true }
}
return conn as java.net.HttpURLConnection
}
/**
* On resume: if a previous session detected an available update and saved it to prefs,
* restore the update banner immediately without a network round-trip.
@@ -540,6 +540,11 @@ class SetupActivity : AppCompatActivity() {
// Cancel auto-discover when leaving server step
if (step != 3) discoverCancelled.set(true)
// Auto-discover when entering server step (empty URL only)
if (step == 3 && urlEdit.text.toString().trim().isEmpty()) {
autoDiscover()
}
// Scroll to top
try { findViewById<ScrollView>(R.id.setupScrollView).scrollTo(0, 0) } catch (_: Exception) {}
}
@@ -697,6 +702,58 @@ class SetupActivity : AppCompatActivity() {
})
}
private fun normalizeDiscoveredBase(urlStr: String): String {
var base = urlStr.substringBefore("/api/")
if (base.endsWith(":443")) base = base.removeSuffix(":443")
if (base.endsWith(":80")) base = base.removeSuffix(":80")
return if (base.endsWith("/")) base else "$base/"
}
private fun probeEverShelfEndpoint(urlStr: String): String? {
return try {
val conn = openConn(urlStr) ?: return null
val code = conn.responseCode
if (code !in 200..399) {
conn.disconnect()
return null
}
val body = conn.inputStream.bufferedReader().readText()
conn.disconnect()
if (body.contains("gemini_key_set") || body.contains("\"success\"") || body.contains("\"ok\"")) {
normalizeDiscoveredBase(urlStr)
} else null
} catch (_: Exception) {
null
}
}
private fun probeEverShelfHost(ip: String, port: Int): String? {
val reachable = try {
Socket().use { s -> s.connect(InetSocketAddress(ip, port), 800); true }
} catch (_: Exception) {
false
}
if (!reachable) return null
val scheme = if (port == 443 || port == 8443) "https" else "http"
val portInUrl = when {
scheme == "https" && port == 443 -> ""
scheme == "http" && port == 80 -> ""
else -> ":$port"
}
val paths = listOf(
"/dispensa/api/index.php?action=ping",
"/api/index.php?action=ping",
"/dispensa/api/index.php?action=get_settings",
"/api/index.php?action=get_settings",
"/evershelf/api/index.php?action=get_settings",
)
for (path in paths) {
probeEverShelfEndpoint("$scheme://$ip$portInUrl$path")?.let { return it }
}
return null
}
private fun openConn(urlStr: String): HttpURLConnection? {
return try {
val conn = URL(urlStr).openConnection()
@@ -772,9 +829,52 @@ class SetupActivity : AppCompatActivity() {
runOnUiThread { discoverStatus.text = "📡 $detectedLabel" }
val ports = listOf(443, 80, 8080, 8443)
// ── 1b. Fast path: likely hosts on Wi-Fi subnet (incl. .128) before full sweep ─
val priorityIps = linkedSetOf<String>()
try {
val ifaces = NetworkInterface.getNetworkInterfaces()
while (ifaces != null && ifaces.hasMoreElements()) {
val intf = ifaces.nextElement()
if (!intf.isUp || intf.isLoopback) continue
for (addr in intf.interfaceAddresses) {
val ip = addr.address
if (ip is java.net.Inet4Address && !ip.isLoopbackAddress) {
priorityIps.add(ip.hostAddress ?: continue)
}
}
}
} catch (_: Exception) {}
for (subnet in wifiSubnets.ifEmpty { subnets.take(1) }) {
for (last in listOf(1, 128, 100, 10, 50, 254)) {
priorityIps.add("$subnet.$last")
}
}
runOnUiThread { discoverStatus.text = "🔍 ${getString(R.string.setup_discovering_detail)}" }
for (ip in priorityIps) {
if (discoverCancelled.get()) break
for (port in ports) {
val hit = probeEverShelfHost(ip, port)
if (hit != null) {
runOnUiThread {
urlEdit.setText(hit)
discoverStatus.text = "${getString(R.string.setup_server_found)}: $hit"
discoverStatus.setTextColor(0xFF34d399.toInt())
showUrlStatus("${getString(R.string.setup_server_found)}", true)
btnDiscover.isEnabled = true
btnDiscover.text = getString(R.string.setup_discover_btn)
}
return@Thread
}
}
}
val paths = listOf(
"/api/index.php?action=get_settings",
"/dispensa/api/index.php?action=ping",
"/api/index.php?action=ping",
"/dispensa/api/index.php?action=get_settings",
"/api/index.php?action=get_settings",
"/evershelf/api/index.php?action=get_settings",
)
@@ -819,30 +919,24 @@ class SetupActivity : AppCompatActivity() {
// Full HTTP probe on reachable host
val scheme = if (port == 443 || port == 8443) "https" else "http"
val portInUrl = when {
scheme == "https" && port == 443 -> ""
scheme == "http" && port == 80 -> ""
else -> ":$port"
}
for (path in paths) {
if (discoverCancelled.get() || found.get()) break
val urlStr = "$scheme://$ip:$port$path"
try {
val conn = openConn(urlStr) ?: continue
val code = conn.responseCode
if (code in 200..399) {
val body = conn.inputStream.bufferedReader().readText()
conn.disconnect()
if (body.contains("gemini_key_set") || body.contains("\"success\"")) {
return@submit urlStr.substringBefore("/api/") + "/"
}
} else conn.disconnect()
} catch (_: Exception) {}
probeEverShelfEndpoint("$scheme://$ip$portInUrl$path")?.let { return@submit it }
}
null
}
}
// ── 3. Collect results as they complete (not in submission order) ────
// ── 3. Collect results until all tasks finish or a server is found ────
var result: String? = null
var collected = 0
while (collected < total && !discoverCancelled.get()) {
val future = cs.poll(3, TimeUnit.SECONDS) ?: break
while (collected < total && !discoverCancelled.get() && result == null) {
val future = cs.poll(500, TimeUnit.MILLISECONDS) ?: continue
collected++
val r = try { future.get() } catch (_: Exception) { null }
if (r != null && found.compareAndSet(false, true)) {
@@ -1101,9 +1195,9 @@ class SetupActivity : AppCompatActivity() {
val lanIp = getDeviceLanIp() ?: "127.0.0.1"
append(",\"scale_enabled\":true,\"scale_gateway_url\":\"ws://$lanIp:8765\"")
}
if (geminiKey.isNotEmpty()) append(",\"gemini_api_key\":\"${geminiKey.replace("\"", "\\\"\")}\"")
if (bringEmail.isNotEmpty()) append(",\"bring_email\":\"${bringEmail.replace("\"", "\\\"\")}\"")
if (bringPassword.isNotEmpty()) append(",\"bring_password\":\"${bringPassword.replace("\"", "\\\"\")}\"")
if (geminiKey.isNotEmpty()) append(",\"gemini_api_key\":\"${geminiKey.replace("\"", "\\\"")}\"")
if (bringEmail.isNotEmpty()) append(",\"bring_email\":\"${bringEmail.replace("\"", "\\\"")}\"")
if (bringPassword.isNotEmpty()) append(",\"bring_password\":\"${bringPassword.replace("\"", "\\\"")}\"")
append("}")
}
val conn = (java.net.URL(url).openConnection() as java.net.HttpURLConnection).apply {
@@ -49,7 +49,7 @@
<string name="install_error_download">Download fehlgeschlagen</string>
<string name="install_error_download_detail">Verbindung prüfen und erneut versuchen.</string>
<string name="install_error_install">Installation fehlgeschlagen</string>
<string name="install_perm_detail">Aktiviere 'Unbekannte Apps installieren' in den Einstellungen, dann komm zurück.</string>
<string name="install_perm_detail">Aktiviere \'Unbekannte Apps installieren\' in den Einstellungen, dann komm zurück.</string>
<string name="install_btn_retry">↩ Nochmal versuchen</string>
<string name="btn_back">Zurück</string>
<string name="btn_launch">🚀 EverShelf starten</string>
@@ -72,15 +72,11 @@
<string name="setup_zerowaste_toggle_label">Zero-Waste-Tipps</string>
<string name="setup_zerowaste_toggle_hint">Beim Kochen Tipps zur Wiederverwendung von Resten anzeigen (Schalen, Kochwasser usw.).</string>
<string name="setup_gemini_title">Google Gemini AI</string>
<string name="setup_gemini_desc">EverShelf nutzt Google Gemini AI für Rezeptvorschläge, smarte Einkaufsschätzungen und mehr.
Zum Aktivieren den kostenlosen Gemini API-Schlüssel eingeben.</string>
<string name="setup_gemini_how">Kostenlosen Schlüssel unter: aistudio.google.com → "API-Schlüssel erhalten"</string>
<string name="setup_gemini_desc">EverShelf nutzt Google Gemini AI für Rezeptvorschläge, smarte Einkaufsschätzungen und mehr.\n\nZum Aktivieren den kostenlosen Gemini API-Schlüssel eingeben.</string>
<string name="setup_gemini_how">Kostenlosen Schlüssel unter: aistudio.google.com → \"API-Schlüssel erhalten\"</string>
<string name="setup_gemini_hint">API-Schlüssel einfügen (beginnt mit AIza…)</string>
<string name="setup_bring_title">Bring! Einkaufsliste</string>
<string name="setup_bring_desc">EverShelf kann die Einkaufsliste mit der Bring!-App synchronisieren.
Bring!-Zugangsdaten eingeben, um die Integration zu aktivieren.</string>
<string name="setup_bring_desc">EverShelf kann die Einkaufsliste mit der Bring!-App synchronisieren.\n\nBring!-Zugangsdaten eingeben, um die Integration zu aktivieren.</string>
<string name="setup_bring_email_hint">Bring!-E-Mail-Adresse</string>
<string name="setup_bring_pass_hint">Bring!-Passwort</string>
<string name="setup_done_title">Alles bereit!</string>
@@ -49,7 +49,7 @@
<string name="install_error_download">Descarga fallida</string>
<string name="install_error_download_detail">Comprueba la conexión e inténtalo de nuevo.</string>
<string name="install_error_install">Instalación fallida</string>
<string name="install_perm_detail">Habilita 'Instalar apps desconocidas' en los ajustes y vuelve aquí.</string>
<string name="install_perm_detail">Habilita \'Instalar apps desconocidas\' en los ajustes y vuelve aquí.</string>
<string name="install_btn_retry">↩ Reintentar</string>
<string name="btn_back">Atrás</string>
<string name="btn_launch">🚀 Iniciar EverShelf</string>
@@ -72,15 +72,11 @@
<string name="setup_zerowaste_toggle_label">Consejos zero-waste</string>
<string name="setup_zerowaste_toggle_hint">Muestra consejos para reutilizar restos (cáscaras, agua de cocción, etc.) al cocinar.</string>
<string name="setup_gemini_title">Google Gemini AI</string>
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI para sugerencias de recetas, estimaciones inteligentes de la compra y más.
Para activarla, introduce tu clave API de Gemini gratuita.</string>
<string name="setup_gemini_how">Obtén tu clave gratuita en: aistudio.google.com → "Obtener clave API"</string>
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI para sugerencias de recetas, estimaciones inteligentes de la compra y más.\n\nPara activarla, introduce tu clave API de Gemini gratuita.</string>
<string name="setup_gemini_how">Obtén tu clave gratuita en: aistudio.google.com → \"Obtener clave API\"</string>
<string name="setup_gemini_hint">Pega la clave API aquí (empieza por AIza…)</string>
<string name="setup_bring_title">Bring! Lista de la compra</string>
<string name="setup_bring_desc">EverShelf puede sincronizar tu lista de la compra con la app Bring!.
Introduce tus credenciales de Bring! para activar la integración.</string>
<string name="setup_bring_desc">EverShelf puede sincronizar tu lista de la compra con la app Bring!.\n\nIntroduce tus credenciales de Bring! para activar la integración.</string>
<string name="setup_bring_email_hint">Correo electrónico de Bring!</string>
<string name="setup_bring_pass_hint">Contraseña de Bring!</string>
<string name="setup_done_title">¡Todo listo!</string>
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EverShelf Kiosk</string>
<string name="setup_enter_url">Veuillez d'abord saisir une URL</string>
<string name="setup_enter_url">Veuillez d\'abord saisir une URL</string>
<string name="setup_testing">Test de connexion…</string>
<string name="setup_server_found">Serveur EverShelf trouvé et API active !</string>
<string name="setup_api_not_found">Serveur accessible mais API EverShelf introuvable. Vérifiez le chemin.</string>
<string name="setup_unreachable">Impossible d'atteindre le serveur</string>
<string name="setup_unreachable">Impossible d\'atteindre le serveur</string>
<string name="setup_discover_btn">🔍 Rechercher sur le réseau local</string>
<string name="setup_perms_granted_next">✅ Permissions accordées — Continuer →</string>
<string name="setup_discovering">Analyse en cours…</string>
<string name="setup_discovering_detail">Recherche de serveurs EverShelf sur le réseau local…</string>
<string name="setup_discover_not_found">Aucun serveur EverShelf trouvé automatiquement. Entrez l'URL manuellement.</string>
<string name="setup_discover_not_found">Aucun serveur EverShelf trouvé automatiquement. Entrez l\'URL manuellement.</string>
<string name="setup_exit_title">Quitter la configuration ?</string>
<string name="setup_exit_message">Vous pouvez terminer la configuration plus tard en rouvrant l'app.</string>
<string name="setup_exit_message">Vous pouvez terminer la configuration plus tard en rouvrant l\'app.</string>
<string name="setup_exit_confirm">Quitter</string>
<string name="setup_exit_cancel">Continuer</string>
<string name="setup_step_back">← Retour</string>
@@ -22,20 +22,20 @@
<string name="wizard_step3_title">Balance intelligente</string>
<string name="wizard_step3_description">EverShelf Kiosk inclut une passerelle Bluetooth intégrée — aucune app externe nécessaire. Sélectionnez votre balance ci-dessous.</string>
<string name="wizard_step3_question">Avez-vous une balance intelligente Bluetooth ?</string>
<string name="wizard_step3_yes">✅ Oui, j'ai une balance</string>
<string name="wizard_step3_yes">✅ Oui, j\'ai une balance</string>
<string name="wizard_step3_no">➡️ Non, ignorer cette étape</string>
<string name="ble_scanning">🔍 Scan en cours…</string>
<string name="ble_connected">Connecté ! Posez un objet sur la balance…</string>
<string name="ble_disconnected">Connexion perdue. Réessayer.</string>
<string name="ble_no_scale_found">Aucune balance trouvée. Vérifiez qu'elle est allumée et à proximité, puis réessayez.</string>
<string name="ble_no_scale_found">Aucune balance trouvée. Vérifiez qu\'elle est allumée et à proximité, puis réessayez.</string>
<string name="ble_select_from_list">Sélectionnez votre balance dans la liste.</string>
<string name="ble_not_confirmed">Balance non confirmée. Relancer le scan.</string>
<string name="ble_scan_again">🔄 Scanner à nouveau</string>
<string name="ble_weight_received">Poids reçu — correspond-il à l'affichage de la balance ?</string>
<string name="ble_weight_received">Poids reçu — correspond-il à l\'affichage de la balance ?</string>
<string name="wizard_gateway_installed">Balance enregistrée ✅</string>
<string name="wizard_gateway_installed_detail">La passerelle BLE intégrée se connectera automatiquement au démarrage.</string>
<string name="wizard_gateway_not_installed">Aucune balance sélectionnée</string>
<string name="wizard_gateway_not_installed_detail">Scannez les balances BLE à proximité et appuyez sur l'une d'elles pour la sélectionner.</string>
<string name="wizard_gateway_not_installed_detail">Scannez les balances BLE à proximité et appuyez sur l\'une d\'elles pour la sélectionner.</string>
<string name="wizard_gateway_checking">Scan des balances BLE en cours…</string>
<string name="wizard_gateway_up_to_date">Service BLE de la balance prêt.</string>
<string name="wizard_gateway_update_available">Balance BLE trouvée</string>
@@ -43,13 +43,13 @@
<string name="install_downloading">Téléchargement en cours…</string>
<string name="install_downloading_detail">Veuillez patienter, le fichier est en cours de téléchargement.</string>
<string name="install_installing">Installation en cours…</string>
<string name="install_confirm_detail">Confirmez l'installation dans la boîte de dialogue ouverte.</string>
<string name="install_confirm_detail">Confirmez l\'installation dans la boîte de dialogue ouverte.</string>
<string name="install_success">Installé avec succès !</string>
<string name="install_success_detail">L'app a été mise à jour.</string>
<string name="install_success_detail">L\'app a été mise à jour.</string>
<string name="install_error_download">Téléchargement échoué</string>
<string name="install_error_download_detail">Vérifiez la connexion et réessayez.</string>
<string name="install_error_install">Installation échouée</string>
<string name="install_perm_detail">Activez 'Installer des apps inconnues' dans les paramètres, puis revenez ici.</string>
<string name="install_perm_detail">Activez \'Installer des apps inconnues\' dans les paramètres, puis revenez ici.</string>
<string name="install_btn_retry">↩ Réessayer</string>
<string name="btn_back">Retour</string>
<string name="btn_launch">🚀 Lancer EverShelf</string>
@@ -58,13 +58,13 @@
<string name="btn_update_gateway">📥 Mettre à jour Scale Gateway</string>
<string name="wizard_server_checking">Vérification de la connexion au serveur…</string>
<string name="wizard_server_ok">Serveur accessible ✅</string>
<string name="wizard_server_ok_detail">Rapport d'erreurs actif — les échecs d'installation seront envoyés automatiquement aux GitHub Issues.</string>
<string name="wizard_server_ok_detail">Rapport d\'erreurs actif — les échecs d\'installation seront envoyés automatiquement aux GitHub Issues.</string>
<string name="wizard_server_error">Serveur inaccessible ⚠️</string>
<string name="wizard_server_error_detail">Les erreurs n'atteindront pas GitHub Issues. Vérifiez l'URL saisie à l'étape 2.</string>
<string name="wizard_server_error_detail">Les erreurs n\'atteindront pas GitHub Issues. Vérifiez l\'URL saisie à l\'étape 2.</string>
<string name="setup_features_title">Fonctionnalités</string>
<string name="setup_features_desc">Activez les fonctions que vous souhaitez utiliser. Vous pourrez les modifier plus tard dans les paramètres du serveur.</string>
<string name="setup_screensaver_toggle_label">Horloge écran de veille</string>
<string name="setup_screensaver_toggle_hint">Affiche une horloge après 5 min d'inactivité.</string>
<string name="setup_screensaver_toggle_hint">Affiche une horloge après 5 min d\'inactivité.</string>
<string name="setup_prices_toggle_label">Prix liste de courses</string>
<string name="setup_prices_toggle_hint">Estimation automatique du coût de chaque article via IA.</string>
<string name="setup_mealplan_toggle_label">Plan de repas</string>
@@ -72,15 +72,11 @@
<string name="setup_zerowaste_toggle_label">Conseils zéro déchet</string>
<string name="setup_zerowaste_toggle_hint">Affiche des conseils pour réutiliser les restes (peaux, eau de cuisson, etc.) pendant la cuisson.</string>
<string name="setup_gemini_title">Google Gemini AI</string>
<string name="setup_gemini_desc">EverShelf utilise Google Gemini AI pour les suggestions de recettes, les estimations intelligentes des courses et plus encore.
Pour l'activer, entrez votre clé API Gemini gratuite.</string>
<string name="setup_gemini_how">Obtenez votre clé gratuite sur : aistudio.google.com → "Obtenir une clé API"</string>
<string name="setup_gemini_desc">EverShelf utilise Google Gemini AI pour les suggestions de recettes, les estimations intelligentes des courses et plus encore.\n\nPour l\'activer, entrez votre clé API Gemini gratuite.</string>
<string name="setup_gemini_how">Obtenez votre clé gratuite sur : aistudio.google.com → \"Obtenir une clé API\"</string>
<string name="setup_gemini_hint">Collez la clé API ici (commence par AIza…)</string>
<string name="setup_bring_title">Bring! Liste de courses</string>
<string name="setup_bring_desc">EverShelf peut synchroniser votre liste de courses avec l'app Bring!.
Entrez vos identifiants Bring! pour activer l'intégration.</string>
<string name="setup_bring_desc">EverShelf peut synchroniser votre liste de courses avec l\'app Bring!.\n\nEntrez vos identifiants Bring! pour activer l\'intégration.</string>
<string name="setup_bring_email_hint">Adresse e-mail Bring!</string>
<string name="setup_bring_pass_hint">Mot de passe Bring!</string>
<string name="setup_done_title">Tout est prêt !</string>
@@ -10,9 +10,9 @@
<string name="setup_perms_granted_next">✅ Permessi concessi — Continua →</string>
<string name="setup_discovering">Scansione in corso…</string>
<string name="setup_discovering_detail">Ricerca server EverShelf nella rete locale…</string>
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l'URL manualmente.</string>
<string name="setup_discover_not_found">Nessun server EverShelf trovato automaticamente. Inserisci l\'URL manualmente.</string>
<string name="setup_exit_title">Uscire dalla configurazione?</string>
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l'app.</string>
<string name="setup_exit_message">Puoi completare la configurazione più tardi riaprendo l\'app.</string>
<string name="setup_exit_confirm">Esci</string>
<string name="setup_exit_cancel">Continua</string>
<string name="setup_step_back">← Indietro</string>
@@ -28,28 +28,28 @@
<string name="ble_connected">Connesso! Posiziona un oggetto sulla bilancia…</string>
<string name="ble_disconnected">Connessione persa. Riprova.</string>
<string name="ble_no_scale_found">Nessuna bilancia trovata. Assicurati che sia accesa e vicina, poi riprova.</string>
<string name="ble_select_from_list">Seleziona la tua bilancia dall'elenco.</string>
<string name="ble_select_from_list">Seleziona la tua bilancia dall\'elenco.</string>
<string name="ble_not_confirmed">Bilancia non confermata. Riprova la scansione.</string>
<string name="ble_scan_again">🔄 Scansiona di nuovo</string>
<string name="ble_weight_received">Peso ricevuto — coincide con quello sulla bilancia?</string>
<string name="wizard_gateway_installed">Bilancia salvata ✅</string>
<string name="wizard_gateway_installed_detail">Il gateway BLE integrato si collegherà automaticamente all'avvio.</string>
<string name="wizard_gateway_installed_detail">Il gateway BLE integrato si collegherà automaticamente all\'avvio.</string>
<string name="wizard_gateway_not_installed">Nessuna bilancia selezionata</string>
<string name="wizard_gateway_not_installed_detail">Scansiona le bilance BLE nelle vicinanze e tocca una per selezionarla.</string>
<string name="wizard_gateway_checking">Scansione bilance BLE in corso…</string>
<string name="wizard_gateway_up_to_date">Servizio BLE bilancia pronto.</string>
<string name="wizard_gateway_update_available">Bilancia BLE trovata</string>
<string name="wizard_gateway_update_detail">Tocca la bilancia nell'elenco per connettersi.</string>
<string name="wizard_gateway_update_detail">Tocca la bilancia nell\'elenco per connettersi.</string>
<string name="install_downloading">Scaricamento in corso…</string>
<string name="install_downloading_detail">Attendi, il file viene scaricato.</string>
<string name="install_installing">Installazione in corso…</string>
<string name="install_confirm_detail">Conferma l'installazione nel dialog che si è aperto.</string>
<string name="install_confirm_detail">Conferma l\'installazione nel dialog che si è aperto.</string>
<string name="install_success">Installato con successo!</string>
<string name="install_success_detail">L'app è stata aggiornata.</string>
<string name="install_success_detail">L\'app è stata aggiornata.</string>
<string name="install_error_download">Download fallito</string>
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
<string name="install_error_install">Installazione fallita</string>
<string name="install_perm_detail">Abilita 'Installa app sconosciute' nelle impostazioni, poi torna qui.</string>
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
<string name="install_btn_retry">↩ Riprova</string>
<string name="btn_back">Indietro</string>
<string name="btn_launch">🚀 Avvia EverShelf</string>
@@ -60,11 +60,11 @@
<string name="wizard_server_ok">Server raggiungibile ✅</string>
<string name="wizard_server_ok_detail">Segnalazione errori attiva — i problemi di installazione vengono inviati automaticamente alle GitHub Issues.</string>
<string name="wizard_server_error">Server non raggiungibile ⚠️</string>
<string name="wizard_server_error_detail">Gli errori non raggiungeranno GitHub Issues. Verifica l'URL inserito al passaggio 2.</string>
<string name="wizard_server_error_detail">Gli errori non raggiungeranno GitHub Issues. Verifica l\'URL inserito al passaggio 2.</string>
<string name="setup_features_title">Funzionalità</string>
<string name="setup_features_desc">Attiva le funzioni che vuoi usare. Puoi sempre cambiarle in seguito dalle impostazioni del server.</string>
<string name="setup_screensaver_toggle_label">Salvaschermo orologio</string>
<string name="setup_screensaver_toggle_hint">Mostra l'overlay orologio dopo 5 min di inattività.</string>
<string name="setup_screensaver_toggle_hint">Mostra l\'overlay orologio dopo 5 min di inattività.</string>
<string name="setup_prices_toggle_label">Prezzi lista spesa</string>
<string name="setup_prices_toggle_hint">Stima automatica del costo di ogni articolo in lista tramite AI.</string>
<string name="setup_mealplan_toggle_label">Piano pasti</string>
@@ -72,15 +72,11 @@
<string name="setup_zerowaste_toggle_label">Suggerimenti zero-waste</string>
<string name="setup_zerowaste_toggle_hint">Durante la cottura mostra consigli per riutilizzare scarti (bucce, acqua di cottura, ecc.).</string>
<string name="setup_gemini_title">Google Gemini AI</string>
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI per suggerimenti di ricette, stime intelligenti della spesa e altro ancora.
Per abilitarla, inserisci la tua chiave API Gemini gratuita.</string>
<string name="setup_gemini_how">Ottieni la chiave gratuita su: aistudio.google.com → "Ottieni chiave API"</string>
<string name="setup_gemini_desc">EverShelf usa Google Gemini AI per suggerimenti di ricette, stime intelligenti della spesa e altro ancora.\n\nPer abilitarla, inserisci la tua chiave API Gemini gratuita.</string>
<string name="setup_gemini_how">Ottieni la chiave gratuita su: aistudio.google.com → \"Ottieni chiave API\"</string>
<string name="setup_gemini_hint">Incolla la chiave API (inizia con AIza…)</string>
<string name="setup_bring_title">Bring! Lista della spesa</string>
<string name="setup_bring_desc">EverShelf può sincronizzare la lista della spesa con l'app Bring!.
Inserisci le credenziali del tuo account Bring! per abilitare l'integrazione.</string>
<string name="setup_bring_desc">EverShelf può sincronizzare la lista della spesa con l\'app Bring!.\n\nInserisci le credenziali del tuo account Bring! per abilitare l\'integrazione.</string>
<string name="setup_bring_email_hint">Email Bring!</string>
<string name="setup_bring_pass_hint">Password Bring!</string>
<string name="setup_done_title">Tutto pronto!</string>
+164 -58
View File
@@ -94,7 +94,7 @@
<div id="preloader-warnings" class="preloader-warnings" style="display:none"></div>
<div id="preloader-error-msg" class="preloader-error-msg" style="display:none"></div>
<button id="preloader-retry-btn" class="preloader-retry-btn" style="display:none" onclick="_startupRetry()">🔄 <span data-i18n="startup.retry">Riprova</span></button>
<span class="app-preloader-version" id="preloader-version">v1.7.39</span>
<span class="app-preloader-version" id="preloader-version">v1.7.42</span>
</div>
</div>
@@ -107,7 +107,7 @@
<!-- Title — left-aligned; grows to fill space -->
<div class="header-title-wrap">
<h1 class="header-title" onclick="showPage('dashboard')">
<img src="assets/img/logo/logo_icon.png" alt="" class="header-logo-icon" aria-hidden="true" /><span data-i18n="nav.title">EverShelf</span><span class="header-version">v1.7.39</span>
<img src="assets/img/logo/logo_icon.png" alt="" class="header-logo-icon" aria-hidden="true" /><span data-i18n="nav.title">EverShelf</span><span class="header-version">v1.7.42</span>
</h1>
<!-- Update badge — shown alongside title, never replaces it -->
<span class="header-update-badge" id="header-update-badge" style="display:none"></span>
@@ -126,6 +126,7 @@
<button class="header-btn header-gemini-btn" onclick="showPage('chat')" title="Chat con Gemini" data-i18n-title="chat.title">
<svg class="gemini-icon" viewBox="0 0 24 24" width="24" height="24" fill="white"><path d="M12 0C12 6.627 6.627 12 0 12c6.627 0 12 5.373 12 12 0-6.627 5.373-12 12-12-6.627 0-12-5.373-12-12z"/></svg>
</button>
<button class="header-btn" onclick="startManualEntry()" title="Ajouter manuellement">✏️</button>
<button class="header-btn header-scan-btn" id="btn-header-scan"
title="Scansiona prodotto (tieni premuto per modalità spesa)" data-i18n-title="scan.hint">
📷
@@ -147,21 +148,6 @@
<!-- ===== DASHBOARD ===== -->
<section class="page active" id="page-dashboard">
<div class="dashboard-stats" id="dashboard-stats">
<div class="stat-card" onclick="showPage('inventory', 'dispensa')">
<span class="stat-icon">🗄️</span>
<span class="stat-value" id="stat-dispensa">-</span>
<span class="stat-label" data-i18n="locations.dispensa">Dispensa</span>
</div>
<div class="stat-card" onclick="showPage('inventory', 'frigo')">
<span class="stat-icon">🧊</span>
<span class="stat-value" id="stat-frigo">-</span>
<span class="stat-label" data-i18n="locations.frigo">Frigo</span>
</div>
<div class="stat-card" onclick="showPage('inventory', 'freezer')">
<span class="stat-icon">❄️</span>
<span class="stat-value" id="stat-freezer">-</span>
<span class="stat-label" data-i18n="locations.freezer">Freezer</span>
</div>
<div class="stat-card" onclick="showPage('shopping')">
<span class="stat-icon">🛒</span>
<span class="stat-value" id="stat-spesa">-</span>
@@ -404,7 +390,7 @@
<form class="form" onsubmit="submitAdd(event)">
<div class="form-group">
<label data-i18n="add.location_label">📍 Dove lo metti?</label>
<div class="location-selector">
<div class="location-selector" id="location-selector-add">
<button type="button" class="loc-btn active" onclick="selectLocation(this, 'dispensa')">🗄️ <span data-i18n="locations.dispensa">Dispensa</span></button>
<button type="button" class="loc-btn" onclick="selectLocation(this, 'frigo')">🧊 <span data-i18n="locations.frigo">Frigo</span></button>
<button type="button" class="loc-btn" onclick="selectLocation(this, 'freezer')">❄️ <span data-i18n="locations.freezer">Freezer</span></button>
@@ -420,6 +406,7 @@
<input type="number" id="add-quantity" value="1" min="0.1" step="any" class="qty-input">
<button type="button" class="qty-btn" onclick="adjustAddQty(1)">+</button>
</div>
<span class="qty-unit-badge qty-unit-muted" id="add-quantity-unit" aria-live="polite">pz</span>
<select id="add-unit" class="form-input unit-select" onchange="onAddUnitChange()">
<option value="pz">pz</option>
<option value="conf">conf</option>
@@ -451,8 +438,7 @@
<p class="form-hint" id="add-vacuum-hint" style="display:none" data-i18n="add.vacuum_hint">La scadenza verrà estesa automaticamente</p>
</div>
<div class="form-group" id="add-expiry-section">
<!-- Populated dynamically by showAddForm() -->
</div>
</div>
<button type="submit" class="btn btn-large btn-success full-width" data-i18n="add.submit">✅ Aggiungi</button>
</form>
</section>
@@ -499,11 +485,14 @@
</button>
<div class="use-partial">
<p id="use-partial-hint" data-i18n="use.partial_hint">Oppure specifica la quantità usata:</p>
<div class="qty-control">
<button type="button" class="qty-btn" id="use-qty-minus" onclick="adjustUseQty(-1)"></button>
<input type="number" id="use-quantity" value="1" min="0.1" step="any" class="qty-input"
oninput="_scaleUserDismissed=true; _cancelScaleTimersOnly();">
<button type="button" class="qty-btn" id="use-qty-plus" onclick="adjustUseQty(1)">+</button>
<div class="qty-control-with-unit">
<div class="qty-control">
<button type="button" class="qty-btn" id="use-qty-minus" onclick="adjustUseQty(-1)"></button>
<input type="number" id="use-quantity" value="1" min="0.1" step="any" class="qty-input"
oninput="_scaleUserDismissed=true; _cancelScaleTimersOnly();">
<button type="button" class="qty-btn" id="use-qty-plus" onclick="adjustUseQty(1)">+</button>
</div>
<span class="qty-unit-badge" id="use-quantity-unit" aria-live="polite"></span>
</div>
<button type="submit" id="btn-use-submit" class="btn btn-large btn-warning full-width mt-2 move-countdown-btn" data-i18n="use.submit">📤 Usa questa quantità</button>
</div>
@@ -656,6 +645,12 @@
<option value="altro">📦 Altro</option>
</select>
</div>
<div class="form-group" id="pf-subcategory-group" style="display:none">
<label>📂 Sous-catégorie <span class="subcategory-required-mark" style="display:none;color:#e74c3c">*</span></label>
<select id="pf-subcategory" class="form-input">
<option value="">-- Aucune --</option>
</select>
</div>
<div class="form-row">
<div class="form-group flex-1">
<label data-i18n="product.unit_label">📏 Unità di misura</label>
@@ -724,6 +719,15 @@
✨ Genera nuova ricetta
</button>
<div id="recipe-archive" class="recipe-archive"></div>
<div class="settings-section" style="margin-top:24px">
<h3 class="settings-section-title">📖 Mes recettes</h3>
<p class="settings-hint">Tes propres recettes (cocktails, boissons...), ajoutées à la main.</p>
<button class="btn btn-large btn-accent full-width" style="margin-top:10px" onclick="openRecipeLibraryForm()"> Ajouter une recette</button>
<button class="btn btn-large btn-secondary full-width" style="margin-top:8px" onclick="openRecipeLibraryImportForm()">📋 Importer texte brut</button>
<div id="recipe-library-tag-filter" style="display:flex;flex-wrap:wrap;gap:6px;margin-top:14px"></div>
<div id="recipe-library-list" style="margin-top:14px"></div>
</div>
</div>
</section>
@@ -1586,6 +1590,17 @@
</div>
<button class="btn btn-large btn-accent full-width" onclick="_backupNow()" id="btn-backup-now" data-i18n="settings.backup.backup_now">💾 Backup Ora</button>
<div id="backup-status" style="display:none;margin-top:8px" class="settings-status"></div>
<div style="margin-top:14px;padding-top:14px;border-top:1px solid var(--border,#e2e8f0)">
<h4 style="margin-bottom:6px">📦 Export / Import complet</h4>
<p class="settings-hint">Exporte la DB + config en un .zip, ou importe un export pour fusionner sans doublons.</p>
<a href="api/index.php?action=export_full" class="btn btn-large btn-accent full-width" style="text-decoration:none;display:block;text-align:center;margin-top:8px">📤 Exporter tout</a>
<div style="margin-top:8px">
<input type="file" id="import-merge-file" accept=".zip" style="display:none" onchange="_importMergeFile(this)">
<button class="btn btn-large btn-secondary full-width" onclick="document.getElementById('import-merge-file').click()">📥 Importer (fusion)</button>
</div>
<div id="import-merge-status" style="display:none;margin-top:8px" class="settings-status"></div>
</div>
<!-- List of backups -->
<div id="backup-list-container" style="margin-top:14px">
<p class="settings-hint" data-i18n="settings.info.loading">Caricamento…</p>
@@ -1692,18 +1707,6 @@
</div>
</div>
<!-- Kiosk app download banner (hidden inside kiosk WebView) -->
<div id="kiosk-download-banner" style="background:linear-gradient(135deg,rgba(16,185,129,0.08),rgba(5,150,105,0.12));border:1.5px solid rgba(16,185,129,0.25);border-radius:12px;padding:16px;margin-top:16px">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">
<span style="font-size:1.6rem">📺</span>
<div>
<p style="margin:0;font-weight:700;font-size:0.95rem;color:#065f46">EverShelf Kiosk</p>
<p class="settings-hint" style="margin:2px 0 0" data-i18n="settings.kiosk.hint">Trasforma un tablet Android in un pannello EverShelf sempre acceso, con bilancia BLE integrata.</p>
</div>
</div>
<a href="https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk" target="_blank" rel="noopener noreferrer" class="btn btn-large btn-accent full-width" style="text-decoration:none;display:block;text-align:center;background:linear-gradient(135deg,#059669,#10b981);color:#fff" data-i18n="settings.kiosk.download_btn">📥 Scarica EverShelf Kiosk (APK)</a>
<p class="settings-hint" style="margin-top:8px" data-i18n="settings.kiosk.download_sub">Modalità kiosk full-screen + gateway bilancia integrato. Sorgente: <code>evershelf-kiosk/</code></p>
</div>
<!-- Kiosk native settings panel (visible only inside kiosk WebView) -->
<div id="kiosk-native-settings-panel" style="display:none;background:rgba(99,102,241,0.06);border:1.5px solid rgba(99,102,241,0.2);border-radius:12px;padding:16px;margin-top:16px">
@@ -1736,30 +1739,129 @@
<button class="btn btn-large btn-success full-width mt-2" onclick="saveSettings()" data-i18n="btn.save_config">💾 Salva Configurazione</button>
<div id="settings-status" class="settings-status" style="display:none"></div>
<!-- About & Support -->
<div class="settings-section" style="margin-top:24px">
<h3 class="settings-section-title" data-i18n="about.title">About</h3>
<div class="settings-row" style="justify-content:space-between;align-items:center">
<span class="settings-label" data-i18n="about.version">Version</span>
<span id="about-version-label" class="settings-hint" style="font-family:monospace"></span>
</section>
<!-- ===== CONFIGURATION PAGE ===== -->
<section class="page" id="page-config">
<div class="page-header">
<h2>🔧 Configuration</h2>
</div>
<div class="config-tabs" style="display:flex;gap:6px;margin-bottom:14px;flex-wrap:wrap">
<button class="btn btn-small btn-primary config-tab-btn" data-tab="locations" onclick="showConfigTab('locations')">📍 Emplacements</button>
<button class="btn btn-small btn-secondary config-tab-btn" data-tab="categories" onclick="showConfigTab('categories')">📁 Catégories</button>
<button class="btn btn-small btn-secondary config-tab-btn" data-tab="subcategories" onclick="showConfigTab('subcategories')">📂 Sous-catégories</button>
<button class="btn btn-small btn-secondary config-tab-btn" data-tab="recipetags" onclick="showConfigTab('recipetags')">🏷️ Tags recettes</button>
<button class="btn btn-small btn-secondary config-tab-btn" data-tab="units" onclick="showConfigTab('units')">📏 Unités</button>
</div>
<div class="config-tab-content" id="config-tab-locations">
<div class="settings-card">
<h4>📍 Emplacements de stockage</h4>
<p class="settings-hint">Gère les emplacements disponibles pour ranger tes produits (Frigo, Cave, Bar...).</p>
<div id="locations-list-container" style="margin-top:10px">
<p class="settings-hint">Chargement…</p>
</div>
<div class="form-group mt-2">
<label> Nouvel emplacement</label>
<div class="barcode-input-row">
<input type="text" id="new-location-icon" class="form-input" style="max-width:70px;text-align:center" placeholder="📦" maxlength="4">
<input type="text" id="new-location-label" class="form-input" placeholder="Es: Cave, Bar, Garage..." onkeydown="if(event.key==='Enter'){event.preventDefault();addLocation()}">
<button class="btn btn-accent" onclick="addLocation()"></button>
</div>
</div>
</div>
<div style="margin-top:10px;display:flex;flex-direction:column;gap:8px">
<button class="btn btn-outline full-width" onclick="reportBugManual()" id="btn-report-bug">
🐛 <span data-i18n="about.report_bug">Segnala un problema</span>
</button>
<p class="settings-hint" style="text-align:center;margin:0" data-i18n="about.report_bug_hint">Qualcosa non funziona? Inviaci una segnalazione direttamente dall'app.</p>
<div style="display:flex;gap:8px">
<a class="btn btn-outline full-width" style="text-decoration:none;text-align:center"
href="https://github.com/dadaloop82/EverShelf/blob/main/CHANGELOG.md"
target="_blank" rel="noopener" data-i18n="about.changelog">Changelog</a>
<a class="btn btn-outline full-width" style="text-decoration:none;text-align:center"
href="https://github.com/dadaloop82/EverShelf"
target="_blank" rel="noopener" data-i18n="about.github">GitHub</a>
</div>
<div class="config-tab-content" id="config-tab-categories" style="display:none">
<div class="settings-card">
<h4>📁 Catégories</h4>
<p class="settings-hint">Gère les catégories de produits : icône, libellé, et mots-clés de détection automatique à partir du nom du produit.</p>
<div id="categories-list-container" style="margin-top:10px">
<p class="settings-hint">Chargement…</p>
</div>
<div class="form-group mt-2">
<label> Nouvelle catégorie</label>
<div class="barcode-input-row">
<input type="text" id="new-category-icon" class="form-input" style="max-width:70px;text-align:center" placeholder="📦" maxlength="4">
<input type="text" id="new-category-label" class="form-input" placeholder="Es: Apéritifs, Bébé...">
<button class="btn btn-accent" onclick="addCategory()"></button>
</div>
<input type="text" id="new-category-keywords" class="form-input mt-1" placeholder="Mots-clés séparés par des virgules (ex: chips, apéro, biscuit salé)">
</div>
</div>
</div>
<div class="config-tab-content" id="config-tab-subcategories" style="display:none">
<div class="settings-card">
<h4>📂 Sous-catégories</h4>
<p class="settings-hint">Gère les sous-catégories disponibles pour chaque catégorie de produit.</p>
<div class="form-group">
<label>Catégorie</label>
<select id="subcat-config-category" class="form-input" onchange="onSubcatConfigCategoryChange()"></select>
</div>
<div class="form-group" style="display:flex;align-items:center;gap:8px">
<input type="checkbox" id="subcat-config-required" onchange="toggleSubcategoryRequired()" style="width:auto">
<label for="subcat-config-required" style="margin:0">Sous-catégorie obligatoire pour cette catégorie</label>
</div>
<div id="subcat-list-container" style="margin-top:10px">
<p class="settings-hint">Chargement…</p>
</div>
<div class="form-group mt-2">
<label> Nouvelle sous-catégorie (pour la catégorie sélectionnée)</label>
<div class="barcode-input-row">
<input type="text" id="new-subcat-label" class="form-input" placeholder="Es: 🍷 Vin, 🍗 Poulet..." onkeydown="if(event.key==='Enter'){event.preventDefault();addSubcategoryRow()}">
<button class="btn btn-accent" onclick="addSubcategoryRow()"></button>
</div>
</div>
</div>
</div>
<div class="config-tab-content" id="config-tab-recipetags" style="display:none">
<div class="settings-card">
<h4>🏷️ Tags recettes</h4>
<p class="settings-hint">Gère les tags utilisés pour trier/filtrer "Mes recettes" (cocktails, boissons...).</p>
<div id="recipe-tags-list-container" style="margin-top:10px">
<p class="settings-hint">Chargement…</p>
</div>
<div class="form-group mt-2">
<label> Nouveau tag</label>
<div class="barcode-input-row">
<input type="text" id="new-recipe-tag-icon" class="form-input" style="max-width:70px;text-align:center" placeholder="🏷️" maxlength="4">
<input type="text" id="new-recipe-tag-label" class="form-input" placeholder="Es: Tiki, Sans alcool...">
<input type="text" id="new-recipe-tag-keywords" class="form-input" placeholder="Mots-clés (ex: citron, lime, vinaigre)" onkeydown="if(event.key==='Enter'){event.preventDefault();addRecipeTagConfig()}">
<button class="btn btn-accent" onclick="addRecipeTagConfig()"></button>
</div>
</div>
</div>
</div>
<div class="config-tab-content" id="config-tab-units" style="display:none">
<div class="settings-card">
<h4>📏 Unités</h4>
<p class="settings-hint">Ajoute des unités personnalisées (ex: kg, L) qui se convertissent automatiquement en g/ml/pz pour le stockage, mais s'affichent dans leur propre unité partout dans l'app.</p>
<div id="custom-units-list-container" style="margin-top:10px">
<p class="settings-hint">Chargement…</p>
</div>
<div class="form-group mt-2">
<label> Nouvelle unité</label>
<div class="barcode-input-row">
<input type="text" id="new-unit-icon" class="form-input" style="max-width:70px;text-align:center" placeholder="📏" maxlength="4">
<input type="text" id="new-unit-key" class="form-input" style="max-width:80px" placeholder="Es: kg" maxlength="10">
<input type="text" id="new-unit-label" class="form-input" placeholder="Es: kg (Kilogrammes)">
</div>
<div class="barcode-input-row" style="margin-top:8px">
<select id="new-unit-base" class="form-input">
<option value="g">g (poids)</option>
<option value="ml">ml (volume)</option>
<option value="pz">pz (pièces)</option>
</select>
<input type="number" id="new-unit-factor" class="form-input" placeholder="Facteur (ex: 1000)" min="0.001" step="any">
<button class="btn btn-accent" onclick="addCustomUnitConfig()"></button>
</div>
</div>
</div>
</div>
</section>
<!-- ===== GEMINI CHAT ===== -->
<section class="page" id="page-chat">
<div class="chat-container">
@@ -1816,6 +1918,10 @@
<span class="nav-icon">📋</span>
<span class="nav-label" data-i18n="nav.log">Storico</span>
</button>
<button class="nav-btn" onclick="showPage('config')" data-page="config">
<span class="nav-icon">🔧</span>
<span class="nav-label">Config.</span>
</button>
<button class="nav-btn" onclick="showPage('settings')" data-page="settings">
<span class="nav-icon">⚙️</span>
<span class="nav-label" data-i18n="nav.settings">Config</span>
@@ -1985,6 +2091,6 @@
</div>
</div>
<script src="assets/js/app.js?v=20260606n"></script>
</body>
<script src="assets/js/app.js?v=20260606z"></script>
</body>
</html>
+2 -2
View File
@@ -2,8 +2,8 @@
"name": "EverShelf",
"short_name": "EverShelf",
"description": "Gestione completa della dispensa di casa con scansione barcode",
"version": "1.7.39",
"start_url": "/evershelf/",
"version": "1.7.42",
"start_url": "/",
"display": "standalone",
"background_color": "#f0f4e8",
"theme_color": "#2d5016",
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"version":"1.7.19","version_code":20}
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env php
<?php
/**
* Audit: products depleted in last N days vs shopping list / Bring / smart shopping.
* Usage: php scripts/audit-finished-shopping.php [days]
*/
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/index.php';
$days = max(1, (int)($argv[1] ?? 30));
$db = getDB();
// Recompute smart shopping fresh
ob_start();
smartShopping($db);
$smartJson = ob_get_clean();
$smartData = json_decode($smartJson, true);
$smartItems = $smartData['items'] ?? [];
$smartByPid = [];
$smartByName = [];
foreach ($smartItems as $si) {
foreach ($si['variants'] ?? [] as $v) {
$smartByPid[(int)$v['product_id']] = $si;
}
$smartByPid[(int)$si['product_id']] = $si;
$sn = strtolower(trim($si['shopping_name'] ?? $si['name'] ?? ''));
if ($sn !== '') $smartByName[$sn] = $si;
}
// Bring list
$bringNames = [];
$bringSpecs = [];
$auth = bringAuth();
if ($auth && !empty($auth['bringListUUID'])) {
$listData = bringRequest('GET', "https://api.getbring.com/rest/v2/bringlists/{$auth['bringListUUID']}");
if ($listData && isset($listData['purchase'])) {
foreach ($listData['purchase'] as $bi) {
$k = mb_strtolower($bi['name'] ?? '');
$bringNames[$k] = $bi['name'] ?? '';
$bringSpecs[$k] = $bi['specification'] ?? '';
}
}
}
// Internal shopping list
$shopNames = [];
$shopRows = $db->query("SELECT name, specification FROM shopping_list")->fetchAll(PDO::FETCH_ASSOC);
foreach ($shopRows as $r) {
$shopNames[mb_strtolower($r['name'])] = $r;
}
// Products with zero stock, last activity in window
$rows = $db->query("
SELECT p.id, p.name, p.brand, p.shopping_name, p.unit,
COALESCE((SELECT SUM(i.quantity) FROM inventory i WHERE i.product_id = p.id), 0) AS stock_qty,
(SELECT MAX(t.created_at) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0
AND t.type IN ('out','waste','in')
AND t.created_at >= datetime('now', '-{$days} days')) AS last_activity,
(SELECT MAX(t.created_at) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0
AND t.type IN ('out','waste')
AND t.created_at >= datetime('now', '-{$days} days')) AS last_out,
(SELECT COUNT(*) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0 AND t.type IN ('out','waste')) AS use_count,
(SELECT COUNT(*) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0 AND t.type = 'in') AS buy_count
FROM products p
WHERE COALESCE((SELECT SUM(i.quantity) FROM inventory i WHERE i.product_id = p.id), 0) <= 0.001
AND (SELECT MAX(t.created_at) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0
AND t.type IN ('out','waste','in')
AND t.created_at >= datetime('now', '-{$days} days')) IS NOT NULL
ORDER BY last_activity DESC
")->fetchAll(PDO::FETCH_ASSOC);
$missing = [];
$onList = [];
$suppressed = [];
foreach ($rows as $r) {
$pid = (int)$r['id'];
$generic = trim($r['shopping_name'] ?? '') ?: computeShoppingName($r['name'], '', $r['brand'] ?? '');
$bringKey = mb_strtolower(italianToBring($generic));
$shopKey = mb_strtolower($generic);
$smart = $smartByPid[$pid] ?? $smartByName[mb_strtolower($generic)] ?? null;
$onBring = isset($bringNames[$bringKey]);
$onShop = isset($shopNames[$shopKey]);
$inSmart = $smart !== null && ($smart['urgency'] ?? 'none') !== 'none';
$entry = [
'id' => $pid,
'name' => $r['name'],
'brand' => $r['brand'],
'generic' => $generic,
'last_activity' => $r['last_activity'],
'last_out' => $r['last_out'],
'use_count' => (int)$r['use_count'],
'buy_count' => (int)$r['buy_count'],
'on_bring' => $onBring,
'on_shop' => $onShop,
'in_smart' => $inSmart,
'smart_urgency' => $smart['urgency'] ?? null,
'smart_reasons' => $smart['reasons'] ?? [],
'bring_spec' => $bringSpecs[$bringKey] ?? '',
];
if (!$onBring && !$onShop && !$inSmart) {
$missing[] = $entry;
} elseif ($onBring || $onShop) {
$onList[] = $entry;
} elseif ($inSmart) {
$suppressed[] = $entry; // in smart but not synced yet
} else {
$missing[] = $entry;
}
}
echo "=== Audit prodotti esauriti (ultimi {$days} giorni) ===\n";
echo 'Totale esauriti con attività recente: ' . count($rows) . "\n";
echo 'Già in lista/Bring: ' . count($onList) . "\n";
echo 'In smart shopping ma non in lista: ' . count($suppressed) . "\n";
echo 'MANCANTI (né lista né Bring né smart): ' . count($missing) . "\n\n";
if ($missing) {
echo "--- MANCANTI ---\n";
foreach ($missing as $m) {
echo sprintf(
"- [%d] %s%s → generico: %s | usi:%d acquisti:%d | ultimo:%s\n",
$m['id'],
$m['name'],
$m['brand'] ? " ({$m['brand']})" : '',
$m['generic'],
$m['use_count'],
$m['buy_count'],
$m['last_activity']
);
}
echo "\n";
}
if ($suppressed) {
echo "--- IN SMART MA NON IN LISTA/BRING ---\n";
foreach ($suppressed as $m) {
echo sprintf(
"- [%d] %s → %s | urgenza:%s | %s\n",
$m['id'],
$m['name'],
$m['generic'],
$m['smart_urgency'] ?? '?',
implode(', ', $m['smart_reasons'] ?? [])
);
}
}
// Export JSON for fix script
file_put_contents(
__DIR__ . '/../data/audit_finished_missing.json',
json_encode(['days' => $days, 'missing' => $missing, 'suppressed' => $suppressed], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);
echo "\nReport salvato in data/audit_finished_missing.json\n";
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env php
<?php
/**
* Backfill Bring!/shopping list for products depleted in the last N days.
* Usage: php scripts/backfill-finished-shopping.php [days]
*/
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/index.php';
$days = max(1, (int)($argv[1] ?? RECENTLY_EXHAUSTED_DAYS));
$db = getDB();
$rows = $db->query("
SELECT p.id, p.name, p.shopping_name
FROM products p
WHERE COALESCE((SELECT SUM(i.quantity) FROM inventory i WHERE i.product_id = p.id), 0) <= 0.001
AND (
SELECT MAX(t.created_at) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0 AND t.type IN ('out','waste')
) >= datetime('now', '-{$days} days')
ORDER BY (
SELECT MAX(t.created_at) FROM transactions t
WHERE t.product_id = p.id AND t.undone = 0 AND t.type IN ('out','waste')
) DESC
")->fetchAll(PDO::FETCH_ASSOC);
echo '[' . date('Y-m-d H:i:s') . "] Backfill {$days}d — " . count($rows) . " prodotti esauriti\n";
$added = 0;
$updated = 0;
$skipped = 0;
foreach ($rows as $r) {
$res = bringAddDepletedProduct($db, (int)$r['id']);
if (!empty($res['added'])) {
$added++;
echo " + {$r['name']}{$res['generic_name']}\n";
} elseif (!empty($res['updated'])) {
$updated++;
echo " ~ {$r['name']}{$res['generic_name']}\n";
} else {
$skipped++;
}
}
ob_start();
smartShopping($db);
$json = ob_get_clean();
$decoded = json_decode($json, true);
if ($decoded && !empty($decoded['success'])) {
$decoded['cached_at'] = date('c');
$decoded['cached_ts'] = time();
file_put_contents(
__DIR__ . '/../data/smart_shopping_cache.json',
json_encode($decoded, JSON_UNESCAPED_UNICODE)
);
}
ob_start();
bringSyncFull($db, false);
$sync = json_decode(ob_get_clean(), true);
$auto = $sync['auto_add'] ?? [];
echo '[' . date('Y-m-d H:i:s') . "] bringAddDepleted: added={$added} updated={$updated} skipped={$skipped}\n";
echo '[' . date('Y-m-d H:i:s') . '] bringSync auto_add: ' . json_encode($auto, JSON_UNESCAPED_UNICODE) . "\n";
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env php
<?php
/** Delete all comments on open feature/enhancement backlog issues (English-only tracker policy). */
declare(strict_types=1);
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/lib/github.php';
require_once __DIR__ . '/../api/lib/constants.php';
$token = _ghToken();
if ($token === '') {
fwrite(STDERR, "ERROR: GH_ISSUE_TOKEN not configured\n");
exit(1);
}
function ghRequest(string $token, string $method, string $url, ?array $body = null): array {
$ch = curl_init($url);
$headers = [
'Authorization: token ' . $token,
'Accept: application/vnd.github+json',
'X-GitHub-Api-Version: 2022-11-28',
'User-Agent: EverShelf-Triage/1.0',
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
]);
if ($method === 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
} elseif ($method === 'GET') {
// default
}
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$raw = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['code' => $code, 'body' => $raw];
}
$issues = [122, 121, 120, 119, 118, 117, 116, 115, 114, 106, 105, 104, 103, 102, 101, 97, 93, 81, 80, 79, 69, 67, 65];
$deleted = 0;
foreach ($issues as $num) {
$page = 1;
while (true) {
$url = 'https://api.github.com/repos/' . GH_REPO . "/issues/$num/comments?per_page=100&page=$page";
$r = ghRequest($token, 'GET', $url);
if ($r['code'] !== 200) {
fwrite(STDERR, "#$num list comments HTTP {$r['code']}\n");
break;
}
$comments = json_decode($r['body'], true);
if (!is_array($comments) || empty($comments)) {
break;
}
foreach ($comments as $c) {
$id = (int)($c['id'] ?? 0);
if ($id <= 0) continue;
$dr = ghRequest($token, 'DELETE', 'https://api.github.com/repos/' . GH_REPO . "/issues/comments/$id");
if ($dr['code'] === 204) {
$deleted++;
echo "deleted comment $id on #$num\n";
} else {
fwrite(STDERR, "FAIL delete comment $id on #$num HTTP {$dr['code']}\n");
}
usleep(200000);
}
if (count($comments) < 100) break;
$page++;
}
}
echo "Done. Deleted $deleted comments.\n";
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env php
<?php
/** Reopen wrongly closed feature issues; close resolved auto-report bugs (English). */
declare(strict_types=1);
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/lib/github.php';
require_once __DIR__ . '/../api/lib/constants.php';
$token = _ghToken();
if ($token === '') {
fwrite(STDERR, "ERROR: GH_ISSUE_TOKEN not configured\n");
exit(1);
}
function ghApi(string $token, string $method, string $url, array $payload = []): array {
$ch = curl_init($url);
$headers = [
'Authorization: token ' . $token,
'Accept: application/vnd.github+json',
'X-GitHub-Api-Version: 2022-11-28',
'User-Agent: EverShelf-Triage/1.0',
'Content-Type: application/json',
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 20,
]);
if ($method === 'PATCH') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
} elseif ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
}
$raw = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['http_code' => $code, 'body' => json_decode($raw ?: '{}', true) ?: []];
}
function comment(string $token, int $num, string $body): void {
$r = ghApi($token, 'POST', 'https://api.github.com/repos/' . GH_REPO . "/issues/$num/comments", ['body' => $body]);
echo $r['http_code'] >= 200 && $r['http_code'] < 300 ? "OK comment #$num\n" : "FAIL comment #$num\n";
}
function closeIssue(string $token, int $num): void {
$r = ghApi($token, 'PATCH', 'https://api.github.com/repos/' . GH_REPO . "/issues/$num", ['state' => 'closed']);
echo $r['http_code'] >= 200 && $r['http_code'] < 300 ? "OK close #$num\n" : "FAIL close #$num\n";
}
function reopenIssue(string $token, int $num): void {
$r = ghApi($token, 'PATCH', 'https://api.github.com/repos/' . GH_REPO . "/issues/$num", ['state' => 'open']);
echo $r['http_code'] >= 200 && $r['http_code'] < 300 ? "OK reopen #$num\n" : "FAIL reopen #$num\n";
}
$reopen = [
125 => "Reopened: **voice commands in cooking mode** are not implemented yet (only TTS readout exists). This was closed by mistake during bulk triage — the feature backlog should stay open until hands-free step navigation ships.",
98 => "Reopened: **pin favourite products to the top of inventory** is not implemented yet (recipe favourites #124 are done, but product pinning is a separate request). Closed by mistake — keeping on the backlog.",
];
foreach ($reopen as $num => $msg) {
comment($token, $num, $msg);
reopenIssue($token, $num);
}
$bugs = [
201 => 'Fixed in latest develop: `inventory_use` and `shopping_add` now retry on `SQLITE_BUSY` via `dbWithRetry()` (same pattern as #198).',
202 => 'Fixed: Bring/internal `shopping_add` wrapped in `dbWithRetry()` to survive cron + PWA concurrent writes.',
203 => 'Fixed: `smartShopping()` / `smartShoppingCached()` now call `set_time_limit(120)` so large pantries no longer hit the 30s PHP fatal.',
204 => 'Fixed: same as #203 — smart shopping timeout caused HTTP 500; extended execution limit resolves the crash.',
];
foreach ($bugs as $num => $msg) {
comment($token, $num, $msg . "\n\n_Closed after triage — fix shipped in develop._");
closeIssue($token, $num);
}
echo "Done.\n";
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Download @xenova/transformers runtime + all-MiniLM-L6-v2 for offline category classification.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
VENDOR="$ROOT/assets/vendor/transformers"
MODEL="$VENDOR/Xenova/all-MiniLM-L6-v2"
ONNX="$MODEL/onnx"
BASE="https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main"
mkdir -p "$ONNX"
echo "→ transformers.min.js"
curl -fsSL "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js" \
-o "$VENDOR/transformers.min.js"
for f in config.json tokenizer.json tokenizer_config.json; do
echo "$f"
curl -fsSL "$BASE/$f" -o "$MODEL/$f"
done
echo "→ onnx/model_quantized.onnx (~22 MB)"
curl -fsSL "$BASE/onnx/model_quantized.onnx" -o "$ONNX/model_quantized.onnx"
chown -R www-data:www-data "$VENDOR" 2>/dev/null || true
echo "Done. Model installed under assets/vendor/transformers/"
+44
View File
@@ -0,0 +1,44 @@
<?php
/**
* Full Bring! sync: recompute smart shopping, migrate names, dedupe generics,
* fix specs, remove obsolete items, add missing critical/high.
*
* Usage: php scripts/sync-shopping-bring.php
*/
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit('Forbidden');
}
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/index.php';
$db = getDB();
echo '[' . date('Y-m-d H:i:s') . "] Starting full Bring! sync…\n";
ob_start();
bringSyncFull($db, true);
$json = ob_get_clean();
$result = json_decode($json, true);
if (!$result || empty($result['success'])) {
echo '[' . date('Y-m-d H:i:s') . '] ERROR: ' . ($result['error'] ?? $json) . "\n";
exit(1);
}
echo '[' . date('Y-m-d H:i:s') . '] Smart items: ' . ($result['smart_items'] ?? '?') . "\n";
echo '[' . date('Y-m-d H:i:s') . '] Migrate: ' . json_encode($result['migrate'] ?? [], JSON_UNESCAPED_UNICODE) . "\n";
echo '[' . date('Y-m-d H:i:s') . '] Dedupe: ' . json_encode($result['dedupe'] ?? [], JSON_UNESCAPED_UNICODE) . "\n";
echo '[' . date('Y-m-d H:i:s') . '] Specs: ' . json_encode($result['specs'] ?? [], JSON_UNESCAPED_UNICODE) . "\n";
echo '[' . date('Y-m-d H:i:s') . '] Cleanup: ' . json_encode($result['cleanup'] ?? [], JSON_UNESCAPED_UNICODE) . "\n";
echo '[' . date('Y-m-d H:i:s') . '] Auto-add: ' . json_encode($result['auto_add'] ?? [], JSON_UNESCAPED_UNICODE) . "\n";
if (!empty($result['dedupe_final'])) {
echo '[' . date('Y-m-d H:i:s') . '] Dedupe (final): ' . json_encode($result['dedupe_final'], JSON_UNESCAPED_UNICODE) . "\n";
}
if (!empty($result['cache_restored'])) {
echo '[' . date('Y-m-d H:i:s') . '] Cache restored: ' . $result['cache_restored'] . " items\n";
}
echo '[' . date('Y-m-d H:i:s') . "] Done.\n";
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env php
<?php
/**
* Triage resolved auto-report bugs only (English comments).
* Feature/enhancement backlog issues are never bulk-closed here.
* Usage: php scripts/triage-open-issues.php [--dry-run]
*/
declare(strict_types=1);
define('CRON_MODE', true);
require_once __DIR__ . '/../api/bootstrap.php';
require_once __DIR__ . '/../api/lib/github.php';
require_once __DIR__ . '/../api/lib/constants.php';
$dryRun = in_array('--dry-run', $argv ?? [], true);
$repo = GH_REPO;
$token = _ghToken();
if ($token === '') {
fwrite(STDERR, "ERROR: GH_ISSUE_TOKEN not configured\n");
exit(1);
}
function ghApi(string $token, string $method, string $url, array $payload = []): array {
$ch = curl_init($url);
$headers = [
'Authorization: token ' . $token,
'Accept: application/vnd.github+json',
'X-GitHub-Api-Version: 2022-11-28',
'User-Agent: EverShelf-Triage/1.0',
'Content-Type: application/json',
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 20,
]);
if ($method === 'PATCH') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
} elseif ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
}
$raw = curl_exec($ch);
$code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['http_code' => $code, 'body' => json_decode($raw ?: '{}', true) ?: []];
}
function commentIssue(string $token, string $repo, int $num, string $body, bool $dryRun): bool {
if ($dryRun) {
echo "[dry-run] comment #$num\n";
return true;
}
$r = ghApi($token, 'POST', "https://api.github.com/repos/$repo/issues/$num/comments", ['body' => $body]);
if ($r['http_code'] >= 200 && $r['http_code'] < 300) {
echo "OK comment #$num\n";
return true;
}
fwrite(STDERR, "FAIL comment #$num HTTP {$r['http_code']}: " . json_encode($r['body']) . "\n");
return false;
}
function closeIssue(string $token, string $repo, int $num, bool $dryRun): bool {
if ($dryRun) {
echo "[dry-run] close #$num\n";
return true;
}
$r = ghApi($token, 'PATCH', "https://api.github.com/repos/$repo/issues/$num", ['state' => 'closed']);
if ($r['http_code'] >= 200 && $r['http_code'] < 300) {
echo "OK close #$num\n";
return true;
}
fwrite(STDERR, "FAIL close #$num HTTP {$r['http_code']}: " . json_encode($r['body']) . "\n");
return false;
}
$bugs = [
198 => 'Fixed in develop: `PRAGMA busy_timeout` raised to 10s and `dbWithRetry()` on `updateInventory` retries SQLITE_BUSY when cron and PWA write in parallel.',
199 => 'Duplicate of #198 — same event (`inventory_update` → database locked). Fix: retry + longer busy_timeout.',
196 => 'Fixed in v1.7.38+: `saveProduct` handles duplicate barcodes (merge or 409 JSON) instead of HTTP 500.',
197 => 'PWA side-effect of PHP crash #196 — fixed with duplicate barcode handling in `saveProduct`.',
195 => 'Fixed: `EverLog::request()` always receives strings — `(string)($_SERVER[\'REQUEST_METHOD\'] ?? \'GET\')`.',
193 => 'Same root cause as #195 (TypeError when method was null from CLI).',
194 => 'Fixed: `_applySpesaScanUI` referenced `currentPage` → corrected to `_currentPageId`.',
192 => 'Fixed: TDZ on `enriched` in `renderShoppingItems`.',
191 => 'Fixed: TDZ on `setProgress` / `barEl` in `_runStartupCheck`.',
134 => 'Auto-report for non-writable Docker volume. Mitigations: `_ensureDataDir()`, `_ensureDbWritable()`, Dockerfile chown.',
184 => 'Related to #134: SQLite readonly when `data/` is not writable.',
];
foreach ($bugs as $num => $msg) {
commentIssue($token, $repo, $num, $msg . "\n\n_Closed after triage — fix shipped in develop._", $dryRun);
closeIssue($token, $repo, $num, $dryRun);
}
echo "Done.\n";
+12
View File
@@ -353,6 +353,18 @@
"throw_all_confirm_btn": "🗑️ Ja, entsorgen",
"locations_short": "Orte"
},
"waste": {
"reason_title": "Warum wirfst du es weg?",
"reason_subtitle": "Das hilft uns, ähnliche Verschwendung zu vermeiden.",
"reason_expired": "⏰ Abgelaufen",
"reason_spoiled": "🦠 Verdorben",
"reason_wrong_location": "📍 Falscher Lagerort",
"reason_kept_too_long": "⏳ Zu lange aufbewahrt",
"reason_bought_too_much": "🛒 Zu viel gekauft",
"reason_forgotten": "😴 Vergessen / nicht rechtzeitig genutzt",
"reason_bad_quality": "👎 Schlechte Qualität beim Kauf",
"reason_other": "❓ Sonstiges"
},
"product": {
"title_new": "Neues Produkt",
"title_edit": "Produkt bearbeiten",
+17
View File
@@ -353,6 +353,18 @@
"throw_all_confirm_btn": "🗑️ Yes, discard",
"locations_short": "places"
},
"waste": {
"reason_title": "Why are you discarding it?",
"reason_subtitle": "This helps us prevent similar waste next time.",
"reason_expired": "⏰ Expired",
"reason_spoiled": "🦠 Spoiled / gone bad",
"reason_wrong_location": "📍 Wrong storage (fridge/freezer/pantry)",
"reason_kept_too_long": "⏳ Kept too long",
"reason_bought_too_much": "🛒 Bought too much",
"reason_forgotten": "😴 Forgotten / not used in time",
"reason_bad_quality": "👎 Poor quality when bought",
"reason_other": "❓ Other"
},
"product": {
"title_new": "New Product",
"title_edit": "Edit Product",
@@ -427,6 +439,11 @@
"regen_save_new": "💾 Save to archive & generate a new one",
"close_btn": "✅ Close",
"ingredients_title": "🧾 Ingredients",
"shopping_suggestions_intro": "For an alternative version you'd need (not in pantry — optional):",
"shopping_suggestions_add": "Add to shopping list",
"shopping_suggestions_added": "Added to shopping list",
"unit_for_input": "Unit of measure",
"enter_in": "Enter value in",
"tools_title": "Equipment needed",
"steps_title": "👨‍🍳 Steps",
"no_steps": "No steps available",
+12
View File
@@ -353,6 +353,18 @@
"throw_all_confirm_btn": "🗑️ Sí, desechar",
"locations_short": "ubicaciones"
},
"waste": {
"reason_title": "¿Por qué lo tiras?",
"reason_subtitle": "Nos ayuda a evitar desperdicios similares.",
"reason_expired": "⏰ Caducado",
"reason_spoiled": "🦠 Estropeado",
"reason_wrong_location": "📍 Lugar de guardado incorrecto",
"reason_kept_too_long": "⏳ Guardado demasiado tiempo",
"reason_bought_too_much": "🛒 Comprado de más",
"reason_forgotten": "😴 Olvidado / no usado a tiempo",
"reason_bad_quality": "👎 Mala calidad al comprar",
"reason_other": "❓ Otro"
},
"product": {
"title_new": "Nuevo producto",
"title_edit": "Editar producto",
+19 -1
View File
@@ -353,6 +353,18 @@
"throw_all_confirm_btn": "🗑️ Oui, jeter",
"locations_short": "emplacements"
},
"waste": {
"reason_title": "Pourquoi le jetez-vous ?",
"reason_subtitle": "Cela nous aide à éviter des gaspillages similaires.",
"reason_expired": "⏰ Périmé",
"reason_spoiled": "🦠 Abîmé / gâté",
"reason_wrong_location": "📍 Mauvais emplacement",
"reason_kept_too_long": "⏳ Conservé trop longtemps",
"reason_bought_too_much": "🛒 Acheté en trop grande quantité",
"reason_forgotten": "😴 Oublié / pas utilisé à temps",
"reason_bad_quality": "👎 Mauvaise qualité à l'achat",
"reason_other": "❓ Autre"
},
"product": {
"title_new": "Nouveau produit",
"title_edit": "Modifier le produit",
@@ -459,7 +471,13 @@
"storage_immediately": "À consommer immédiatement",
"stream_interrupted": "Génération interrompue (réponse serveur incomplète). Vérifiez les logs ou réessayez.",
"ing_stock_line": "Vous avez {have} · il reste {remain} après usage",
"ing_use_all_note": "tout utiliser (<5% du conditionnement entier)"
"ing_use_all_note": "tout utiliser (<5% du conditionnement entier)",
"shopping_suggestions_intro": "Pour une variante, il faudrait (pas dans le garde-manger — optionnel) :",
"shopping_suggestions_add": "Ajouter à la liste de courses",
"shopping_suggestions_added": "Ajouté à la liste de courses",
"frozen_badge": "surgelé — du congélateur",
"unit_for_input": "Unité de mesure",
"enter_in": "Saisie en"
},
"shopping": {
"title": "🛒 Liste de courses",
+18
View File
@@ -353,6 +353,18 @@
"throw_all_confirm_btn": "🗑️ Sì, butta",
"locations_short": "posti"
},
"waste": {
"reason_title": "Perché lo butti?",
"reason_subtitle": "Ci aiuta a evitare sprechi simili in futuro.",
"reason_expired": "⏰ Scaduto",
"reason_spoiled": "🦠 Andato a male / deperito",
"reason_wrong_location": "📍 Posto sbagliato (frigo/freezer/dispensa)",
"reason_kept_too_long": "⏳ Tenuto troppo a lungo",
"reason_bought_too_much": "🛒 Comprato troppo",
"reason_forgotten": "😴 Dimenticato / non usato in tempo",
"reason_bad_quality": "👎 Qualità scadente all'acquisto",
"reason_other": "❓ Altro"
},
"product": {
"title_new": "Nuovo Prodotto",
"title_edit": "Modifica Prodotto",
@@ -427,6 +439,12 @@
"regen_save_new": "💾 Salva nell'archivio e genera una nuova",
"close_btn": "✅ Chiudi",
"ingredients_title": "🧾 Ingredienti",
"shopping_suggestions_intro": "Per una variante servirebbe (non in dispensa — opzionale):",
"shopping_suggestions_add": "Aggiungi alla lista spesa",
"shopping_suggestions_added": "Aggiunto alla lista spesa",
"frozen_badge": "surgelato — dal freezer",
"unit_for_input": "Unità di misura",
"enter_in": "Inserimento in",
"tools_title": "Strumenti necessari",
"steps_title": "👨‍🍳 Procedimento",
"no_steps": "Nessun procedimento disponibile",