diff --git a/client/static/js/spc-charts.js b/client/static/js/spc-charts.js index 85c0063..1ed1502 100644 --- a/client/static/js/spc-charts.js +++ b/client/static/js/spc-charts.js @@ -35,14 +35,14 @@ const PLOTLY_LAYOUT_DEFAULTS = { font: { family: 'Inter, system-ui, sans-serif', color: SPC_COLORS.textColor }, paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', - margin: { t: 40, r: 30, b: 50, l: 60 }, + margin: { t: 20, r: 30, b: 50, l: 60 }, xaxis: { gridcolor: SPC_COLORS.gridColor, zeroline: false }, yaxis: { gridcolor: SPC_COLORS.gridColor, zeroline: false }, }; const PLOTLY_CONFIG = { responsive: true, - displayModeBar: true, + displayModeBar: 'hover', modeBarButtonsToRemove: ['lasso2d', 'select2d'], displaylogo: false, }; @@ -154,7 +154,6 @@ function renderControlChart(containerId, data) { const layout = { ...PLOTLY_LAYOUT_DEFAULTS, - title: { text: _t('controlChart'), font: { size: 14 } }, xaxis: { ...PLOTLY_LAYOUT_DEFAULTS.xaxis, title: _t('measureNum') }, yaxis: { ...PLOTLY_LAYOUT_DEFAULTS.yaxis, title: _t('value') }, shapes, @@ -231,7 +230,6 @@ function renderHistogram(containerId, data, tol) { const layout = { ...PLOTLY_LAYOUT_DEFAULTS, - title: { text: _t('histogram'), font: { size: 14 } }, xaxis: { ...PLOTLY_LAYOUT_DEFAULTS.xaxis, title: _t('value') }, yaxis: { ...PLOTLY_LAYOUT_DEFAULTS.yaxis, title: _t('frequency') }, shapes, diff --git a/client/templates/statistics/dashboard.html b/client/templates/statistics/dashboard.html index 685b2fe..f591e7a 100644 --- a/client/templates/statistics/dashboard.html +++ b/client/templates/statistics/dashboard.html @@ -108,6 +108,12 @@ {{ _("Applica filtri") }} + +
+
@@ -266,6 +272,7 @@ function spcDashboard() { loading: false, hasData: false, errorMessage: '', + reportError: '', downloadingReport: false, init() { @@ -387,7 +394,7 @@ function spcDashboard() { renderHistogram('histogram-chart', this.histogram, tol); } else { const el = document.getElementById('histogram-chart'); - if (el) el.innerHTML = '

{{ _("Seleziona un punto di misura per l\'istogramma") }}

'; + if (el) el.innerHTML = '

' + {{ _("Seleziona un punto di misura per l'istogramma")|tojson }} + '

'; } // Capability gauge @@ -404,6 +411,7 @@ function spcDashboard() { }, async downloadReport(type) { + this.reportError = ''; this.downloadingReport = type; try { const params = new URLSearchParams(); @@ -417,7 +425,19 @@ function spcDashboard() { if (!resp.ok) { const errData = await resp.json().catch(() => ({})); - this.errorMessage = errData.detail || '{{ _("Errore nella generazione del report") }}'; + const msg = errData.detail || '{{ _("Errore nella generazione del report") }}'; + console.error('Report download failed (' + resp.status + '):', msg, errData); + this.reportError = msg; + return; + } + + // Check Content-Type: server may return JSON error with 200 status + const contentType = resp.headers.get('Content-Type') || ''; + if (!contentType.includes('application/pdf')) { + const errData = await resp.json().catch(() => ({})); + const msg = errData.detail || '{{ _("Errore nella generazione del report") }}'; + console.error('Report returned non-PDF content-type "' + contentType + '":', msg, errData); + this.reportError = msg; return; } @@ -434,7 +454,7 @@ function spcDashboard() { URL.revokeObjectURL(a.href); } catch (e) { console.error('Error downloading report:', e); - this.errorMessage = '{{ _("Errore di connessione al server") }}'; + this.reportError = '{{ _("Errore di connessione al server") }}'; } finally { this.downloadingReport = false; } diff --git a/server/routers/setup.py b/server/routers/setup.py index bb2a257..7d9e403 100644 --- a/server/routers/setup.py +++ b/server/routers/setup.py @@ -268,6 +268,14 @@ async def seed_demo_data(body: PasswordBody): created_users: list[User] = [] for u in users_data: + # Skip users that already exist + existing = await session.execute( + select(User).where(User.username == u["username"]) + ) + existing_user = existing.scalar_one_or_none() + if existing_user: + created_users.append(existing_user) + continue user = User( username=u["username"], password_hash=hash_password(u["password"]), @@ -286,6 +294,19 @@ async def seed_demo_data(body: PasswordBody): admin_user = created_users[0] # ---- Recipe ------------------------------------------------------- + # Skip if recipe already exists + existing_recipe = await session.execute( + select(Recipe).where(Recipe.code == "DEMO-001") + ) + if existing_recipe.scalar_one_or_none(): + return { + "status": "ok", + "message": "Demo data already exists, skipped", + "users_created": [u["username"] for u in users_data], + "recipe_created": "DEMO-001", + "measurements_created": 0, + } + recipe = Recipe( code="DEMO-001", name="Demo Measurement Recipe", diff --git a/server/templates/setup/setup.html b/server/templates/setup/setup.html index 9bee60a..081b256 100644 --- a/server/templates/setup/setup.html +++ b/server/templates/setup/setup.html @@ -603,7 +603,8 @@ showToast('Setup is disabled. Set SETUP_PASSWORD in .env', 'error'); return; } - // Password accepted (status endpoint is accessible) + // Validate password via init-db (idempotent, no tables needed) + await apiPost('/init-db', { password: pwd }); setupPassword = pwd; document.getElementById('login-screen').classList.add('hidden'); document.getElementById('setup-panel').classList.remove('hidden'); @@ -611,7 +612,11 @@ await loadUsers(); showToast('Setup panel unlocked', 'success'); } catch (e) { - showToast('Connection error: ' + e.message, 'error'); + if (e.message.indexOf('403') !== -1 || e.message.indexOf('Invalid') !== -1) { + showToast('Invalid setup password', 'error'); + } else { + showToast('Connection error: ' + e.message, 'error'); + } } finally { setLoading('btn-login', false); }