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:
@@ -157,3 +157,93 @@ input, textarea, select {
|
||||
font-family: inherit;
|
||||
color: var(--cds-text-primary);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
App shell — shared "UI Shell" chrome (Prime Controls, IBM Carbon styling)
|
||||
----------------------------------------------------------------------------
|
||||
One dark top bar across every page so the suite reads as a single product.
|
||||
The Prime Controls logo is a white-background wordmark, so it sits inside a
|
||||
white "chip" on the near-black bar (reads as intentional, not a stray box).
|
||||
Flip --wp-appbar-bg to a light value if a light header is ever preferred.
|
||||
============================================================================ */
|
||||
:root {
|
||||
--wp-appbar-bg: #161616; /* near-black UI Shell bar */
|
||||
--wp-appbar-fg: #ffffff;
|
||||
--wp-appbar-fg-dim: #c6c6c6;
|
||||
--wp-appbar-border: #6f6f6f; /* outline for ghost buttons on the bar */
|
||||
--wp-appbar-hover: #353535;
|
||||
--wp-appbar-height: 48px;
|
||||
}
|
||||
|
||||
.wp-appbar {
|
||||
background: var(--wp-appbar-bg);
|
||||
color: var(--wp-appbar-fg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: var(--wp-appbar-height);
|
||||
padding: 0 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
.wp-appbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: var(--wp-appbar-fg);
|
||||
}
|
||||
.wp-appbar-brand:hover { text-decoration: none; opacity: .92; }
|
||||
.wp-logo-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.wp-logo-chip img { height: 24px; width: auto; display: block; }
|
||||
.wp-appbar-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--wp-appbar-fg);
|
||||
white-space: nowrap;
|
||||
letter-spacing: .01em;
|
||||
}
|
||||
.wp-appbar-title .wp-appbar-sub { font-weight: 400; color: var(--wp-appbar-fg-dim); }
|
||||
.wp-appbar-spacer { flex: 1 1 auto; }
|
||||
.wp-appbar-meta { font-size: 13px; color: var(--wp-appbar-fg-dim); white-space: nowrap; }
|
||||
.wp-appbar-actions { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
/* Buttons and links that live on the dark bar */
|
||||
.wp-appbar-btn {
|
||||
background: transparent;
|
||||
color: var(--wp-appbar-fg);
|
||||
border: 1px solid var(--wp-appbar-border);
|
||||
border-radius: 0;
|
||||
padding: 7px 14px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
line-height: 1.2;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: background .15s, border-color .15s;
|
||||
}
|
||||
.wp-appbar-btn:hover { background: var(--wp-appbar-hover); color: var(--wp-appbar-fg); text-decoration: none; }
|
||||
.wp-appbar-btn.primary { background: var(--cds-interactive-01); border-color: var(--cds-interactive-01); }
|
||||
.wp-appbar-btn.primary:hover { background: var(--cds-hover-primary); border-color: var(--cds-hover-primary); }
|
||||
.wp-appbar-btn:focus-visible { outline: 2px solid var(--wp-appbar-fg); outline-offset: 1px; }
|
||||
.wp-appbar-count { font-size: 13px; color: var(--wp-appbar-fg-dim); padding: 0 2px; white-space: nowrap; }
|
||||
|
||||
/* Plain text links on the dark bar (Overview / Feedback / Help, Admin, etc.) */
|
||||
.wp-appbar-link { color: var(--wp-appbar-fg-dim); text-decoration: none; font-size: 14px; white-space: nowrap; }
|
||||
.wp-appbar-link:hover { color: var(--wp-appbar-fg); text-decoration: none; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.wp-appbar { height: auto; flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
|
||||
.wp-appbar-actions { flex-wrap: wrap; }
|
||||
.wp-appbar-meta { width: 100%; order: 5; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user