Productionize WP Suite: auth, security hardening, sync, dashboard, PWA, email
Brings the Work Package Suite from a browser-local prototype to a multi-tenant, SQL-backed deployment hardened for customer IP. Auth & access control - Local username/password login (bcrypt + JWT in an HttpOnly cookie), admin-managed users, per-project membership, and project-scoped API access. - Admin console: change user roles, view the audit trail, manage settings. Security hardening - CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping. - Login lockout, token_version session revocation, stronger password policy, fail-closed secret loading, encrypted (AES-256) database backups. Persistence & schema - SOPs and Work Packages are now DB-backed and shared across users, written through a durable client sync outbox that queues offline edits. - Alembic migrations applied automatically on container start. New capabilities - Phase 2 dashboard (progress, gating, pagination, archive). - Phase 3 PWA "Field View" with offline caching and auth fallback. - WP owner assignment with OPTIONAL email notifications, OFF by default and toggled from the admin console. SMTP password is read only from the SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link, never customer IP. Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,25 +7,25 @@
|
||||
body.embedded .embed-first { margin-left: auto; }
|
||||
|
||||
:root {
|
||||
--bg: #f4f5f7;
|
||||
--bg: #f4f4f4;
|
||||
--surface: #ffffff;
|
||||
--surface2: #f7f8fa;
|
||||
--border: #e3e6ec;
|
||||
--border-strong: #d0d5de;
|
||||
--text: #1a2230;
|
||||
--text-muted: #5a6675;
|
||||
--text-dim: #9aa3b2;
|
||||
--accent: #2563d6;
|
||||
--accent-dim: #e8f0fe;
|
||||
--accent-green: #15924f;
|
||||
--accent-green-dim: #e4f6ec;
|
||||
--accent-amber: #b87100;
|
||||
--accent-amber-dim: #fdf2e0;
|
||||
--red: #cf3b3b;
|
||||
--red-dim: #fbeaea;
|
||||
--radius: 5px;
|
||||
--shadow: 0 1px 2px rgba(20,30,50,.04), 0 1px 3px rgba(20,30,50,.06);
|
||||
--shadow-lg: 0 4px 16px rgba(20,30,50,.08);
|
||||
--surface2: #f4f4f4;
|
||||
--border: #e0e0e0;
|
||||
--border-strong: #8d8d8d;
|
||||
--text: #161616;
|
||||
--text-muted: #525252;
|
||||
--text-dim: #8d8d8d;
|
||||
--accent: #0f62fe;
|
||||
--accent-dim: #edf5ff;
|
||||
--accent-green: #198038;
|
||||
--accent-green-dim: #defbe6;
|
||||
--accent-amber: #8e6a00;
|
||||
--accent-amber-dim: #fdf6dd;
|
||||
--red: #da1e28;
|
||||
--red-dim: #fff1f1;
|
||||
--radius: 0;
|
||||
--shadow: none;
|
||||
--shadow-lg: 0 4px 16px rgba(20,30,50,.12);
|
||||
--mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', Consolas, monospace;
|
||||
--sans: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
@@ -213,7 +213,7 @@
|
||||
|
||||
.notice {
|
||||
background: var(--accent-dim); border: 1px solid #b9d2fb; border-radius: var(--radius);
|
||||
padding: 10px 14px; font-size: 12px; color: #1a4fad; margin-bottom: 18px; font-family: var(--mono);
|
||||
padding: 10px 14px; font-size: 12px; color: #0043ce; margin-bottom: 18px; font-family: var(--mono);
|
||||
}
|
||||
|
||||
/* ── DELIVERABLES ── */
|
||||
@@ -245,9 +245,9 @@
|
||||
.btn-ghost { background: var(--surface); border-color: var(--border-strong); color: var(--text-muted); }
|
||||
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: var(--shadow); }
|
||||
.btn-primary:hover { background: #1d52b8; }
|
||||
.btn-primary:hover { background: #0353e9; }
|
||||
.btn-generate { background: var(--accent-green); border-color: var(--accent-green); color: #fff; font-weight: 700; box-shadow: var(--shadow); }
|
||||
.btn-generate:hover { background: #117a42; }
|
||||
.btn-generate:hover { background: #0e6027; }
|
||||
|
||||
/* ── OUTPUT ── */
|
||||
#output-section { display: none; }
|
||||
@@ -419,7 +419,7 @@
|
||||
.ov-select.ov-unset { color:var(--red) !important; border-color:var(--red); }
|
||||
.use-btn { display:inline-block; margin-left:8px; padding:4px 14px; font-family:var(--sans); font-size:11px; font-weight:700;
|
||||
color:#fff; background:var(--accent-green); border:none; border-radius:var(--radius); cursor:pointer; letter-spacing:.03em; }
|
||||
.use-btn:hover { background:#0f7a40; }
|
||||
.use-btn:hover { background:#0e6027; }
|
||||
.sum-chips { display:flex; flex-wrap:wrap; gap:7px; }
|
||||
.sum-chip { background:var(--accent-dim); color:var(--accent); border:1px solid #b9d2fb; border-radius:3px;
|
||||
padding:3px 10px; font-family:var(--mono); font-size:10px; }
|
||||
@@ -495,7 +495,7 @@
|
||||
|
||||
.modal-overlay { position:fixed; inset:0; background:rgba(20,28,40,.55); display:none; align-items:center; justify-content:center; z-index:9000; padding:20px; }
|
||||
.modal-overlay.open { display:flex; }
|
||||
.modal { background:var(--surface); border-radius:12px; width:100%; max-width:520px; box-shadow:0 20px 60px rgba(0,0,0,.3); overflow:hidden; max-height:90vh; display:flex; flex-direction:column; }
|
||||
.modal { background:var(--surface); border-radius:0; width:100%; max-width:520px; box-shadow:0 20px 60px rgba(0,0,0,.3); overflow:hidden; max-height:90vh; display:flex; flex-direction:column; }
|
||||
.modal-head { display:flex; align-items:center; justify-content:space-between; padding:16px 20px; border-bottom:1px solid var(--border); }
|
||||
.modal-title { font-weight:700; font-size:15px; color:var(--text); }
|
||||
.modal-body { padding:18px 20px; overflow-y:auto; }
|
||||
@@ -571,9 +571,11 @@
|
||||
|
||||
/* Section nav (jump chips) */
|
||||
.section-nav-bar{ position:sticky; top:0; z-index:30; display:flex; flex-wrap:wrap; gap:6px;
|
||||
padding:8px 12px; background:rgba(255,255,255,.92); backdrop-filter:blur(4px);
|
||||
border-bottom:1px solid var(--border); }
|
||||
padding:8px 12px; background:rgba(255,255,255,.94); backdrop-filter:blur(4px);
|
||||
border-bottom:1px solid var(--border); box-shadow:0 1px 4px rgba(20,30,50,.06);
|
||||
transition:transform .22s ease; }
|
||||
.section-nav-bar:empty{ display:none; }
|
||||
.section-nav-bar.nav-hidden{ transform:translateY(-160%); }
|
||||
.sec-chip{ font-size:12px; font-weight:600; color:var(--text-muted); background:var(--surface2);
|
||||
border:1px solid var(--border); border-radius:14px; padding:4px 11px; cursor:pointer; white-space:nowrap; }
|
||||
.sec-chip:hover{ border-color:var(--accent); color:var(--accent); }
|
||||
@@ -606,11 +608,12 @@
|
||||
|
||||
/* Dashboard */
|
||||
.dash-metrics { display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:12px; margin-bottom:16px; }
|
||||
.dash-metric { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:14px 16px; text-align:center; }
|
||||
.dash-metric { background:var(--surface); border:1px solid var(--border); border-radius:0; padding:14px 16px; text-align:center; }
|
||||
.dash-metric .dm-val { font-size:26px; font-weight:800; line-height:1; }
|
||||
.dash-metric .dm-label { font-size:11px; color:var(--text-muted); margin-top:6px; text-transform:uppercase; letter-spacing:.03em; }
|
||||
.dash-metric.dm-green .dm-val { color:var(--accent-green); }
|
||||
.dash-metric.dm-red .dm-val { color:var(--red); }
|
||||
.dash-metric.dm-blue .dm-val { color:var(--accent, #0f62fe); }
|
||||
.dash-metric[onclick] { cursor:pointer; transition:border-color .12s, box-shadow .12s; }
|
||||
.dash-metric[onclick]:hover { border-color:var(--accent); }
|
||||
.dash-metric.dm-active { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-dim); }
|
||||
@@ -620,7 +623,7 @@
|
||||
.dash-bd-title { font-size:11px; font-weight:700; text-transform:uppercase; color:var(--text-muted); margin-bottom:6px; }
|
||||
.dash-chip { display:inline-block; font-size:12px; background:var(--surface2); border:1px solid var(--border); border-radius:14px; padding:3px 10px; margin:0 6px 6px 0; }
|
||||
.dash-chip.chip-red { background:var(--red-dim); color:var(--red); border-color:var(--red); }
|
||||
.dash-panel { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:14px 16px; margin-bottom:16px; }
|
||||
.dash-panel { background:var(--surface); border:1px solid var(--border); border-radius:0; padding:14px 16px; margin-bottom:16px; }
|
||||
.dash-panel-title { font-weight:700; font-size:13px; margin-bottom:10px; }
|
||||
.dash-table { width:100%; border-collapse:collapse; font-size:12.5px; }
|
||||
.dash-table th { text-align:left; background:var(--surface2); border-bottom:1px solid var(--border); padding:6px 8px; font-size:11px; text-transform:uppercase; color:var(--text-muted); }
|
||||
@@ -629,3 +632,27 @@
|
||||
.dash-filters input, .dash-filters select { padding:7px 10px; border:1px solid var(--border-strong); border-radius:6px; font-size:13px; }
|
||||
.dash-filters input[type=search] { flex:1; min-width:200px; }
|
||||
@media (max-width:640px){ .dash-breakdown { grid-template-columns:1fr; } }
|
||||
|
||||
/* ── WP history (audit trail) modal ──────────────────────────────────────── */
|
||||
.hist-list { display:flex; flex-direction:column; }
|
||||
.hist-item { display:grid; grid-template-columns:170px 1fr auto; gap:12px; align-items:baseline;
|
||||
padding:9px 2px; border-bottom:1px solid var(--border); }
|
||||
.hist-item:last-child { border-bottom:none; }
|
||||
.hist-when { font-family:var(--mono); font-size:11px; color:var(--text-muted); white-space:nowrap; }
|
||||
.hist-action { font-weight:600; color:var(--text); }
|
||||
.hist-detail { color:var(--accent); font-size:13px; }
|
||||
.hist-actor { font-size:12px; color:var(--text-muted); white-space:nowrap; }
|
||||
@media (max-width:560px){ .hist-item { grid-template-columns:1fr; gap:2px; } }
|
||||
|
||||
/* ── Dashboard progress bars + pager + archived toggle (Phase 2) ──────────── */
|
||||
.prog-row { display:grid; grid-template-columns:150px 1fr 96px; gap:10px; align-items:center; margin-bottom:7px; }
|
||||
.prog-name { font-size:12.5px; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.prog-bar { height:10px; background:var(--surface2); border:1px solid var(--border); overflow:hidden; }
|
||||
.prog-fill { height:100%; background:var(--accent); transition:width .3s ease; }
|
||||
.prog-pct { font-size:12px; font-weight:600; color:var(--text); text-align:right; white-space:nowrap; }
|
||||
.prog-sub { font-weight:400; color:var(--text-muted); font-size:11px; }
|
||||
.dash-arch-toggle { display:inline-flex; align-items:center; gap:6px; font-size:13px; color:var(--text-muted); white-space:nowrap; cursor:pointer; }
|
||||
.dash-pager { display:flex; align-items:center; gap:12px; margin-top:12px; font-size:12.5px; color:var(--text-muted); }
|
||||
.dash-pager-btns { margin-left:auto; display:flex; gap:8px; }
|
||||
.dash-pager .btn { padding:5px 12px; }
|
||||
@media (max-width:560px){ .prog-row { grid-template-columns:110px 1fr 74px; } }
|
||||
|
||||
Reference in New Issue
Block a user