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:
237
html/index.html
237
html/index.html
@@ -6,124 +6,67 @@
|
||||
<title>Work Package Suite — Prime Controls</title>
|
||||
<script src="auth-guard.js"></script>
|
||||
<link rel="icon" href="favicon.ico" sizes="any">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<meta name="theme-color" content="#161616">
|
||||
<link rel="stylesheet" href="theme-light.css">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--cds-background);
|
||||
color: var(--cds-text-primary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
background: var(--cds-layer);
|
||||
padding: 1.5rem 2rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
border-bottom: 1px solid var(--cds-border-subtle);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
color: var(--cds-text-primary);
|
||||
background: white;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.logo:hover { opacity: 0.9; }
|
||||
|
||||
.header-spacer { flex: 1; }
|
||||
|
||||
.header-nav {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-nav a {
|
||||
color: var(--cds-text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.header-nav a:hover { color: var(--cds-text-primary); }
|
||||
|
||||
|
||||
/* CONTAINER */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 2rem;
|
||||
padding: 2.5rem 2rem 3rem;
|
||||
}
|
||||
|
||||
|
||||
/* HERO */
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.625rem;
|
||||
font-size: 2.25rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1rem;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--cds-text-primary);
|
||||
}
|
||||
|
||||
|
||||
.hero p {
|
||||
font-size: 1.125rem;
|
||||
font-size: 1rem;
|
||||
color: var(--cds-text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
max-width: 700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 760px;
|
||||
}
|
||||
|
||||
/* CARDS */
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
background: var(--cds-layer);
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid var(--cds-border-strong);
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
transition: all 0.2s;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
text-decoration: none;
|
||||
color: var(--cds-text-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.4);
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--cds-button-primary);
|
||||
border-left-color: var(--cds-interactive-01);
|
||||
background: var(--cds-layer-hover);
|
||||
}
|
||||
|
||||
.card-badge {
|
||||
@@ -153,10 +96,10 @@
|
||||
|
||||
.card-button {
|
||||
display: inline-block;
|
||||
align-self: flex-start;
|
||||
background: var(--cds-button-primary);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 3px;
|
||||
padding: 0.7rem 1.25rem;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
@@ -165,23 +108,22 @@
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
.card-button:hover {
|
||||
background: var(--cds-hover-primary);
|
||||
}
|
||||
|
||||
/* COMPLETE STATE (SOP done) */
|
||||
.card.complete {
|
||||
background: #ecfdf5;
|
||||
border-color: #16a34a;
|
||||
border-left-color: var(--cds-support-success);
|
||||
}
|
||||
.card.complete .card-button { background: #16a34a; }
|
||||
.card.complete .card-button:hover { background: #15803d; }
|
||||
.card.complete .card-button { background: var(--cds-support-success); }
|
||||
.card.complete .card-button:hover { background: #0e6027; }
|
||||
.card-status {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #16a34a;
|
||||
color: var(--cds-support-success);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.card.disabled {
|
||||
@@ -192,9 +134,8 @@
|
||||
/* SECTION */
|
||||
.section {
|
||||
background: var(--cds-layer);
|
||||
border-radius: 4px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
}
|
||||
|
||||
@@ -217,16 +158,6 @@
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.quick-start {
|
||||
background: var(--cds-button-primary);
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.quick-start h2 { color: white; }
|
||||
.quick-start ol { margin-left: 1.5rem; line-height: 2; }
|
||||
.quick-start li { margin-bottom: 0.5rem; }
|
||||
|
||||
/* FOOTER */
|
||||
.footer {
|
||||
background: var(--cds-ui-01);
|
||||
@@ -247,94 +178,93 @@
|
||||
/* COMMENTS SECTION */
|
||||
.comments-section {
|
||||
background: var(--cds-layer);
|
||||
border-radius: 4px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
}
|
||||
|
||||
|
||||
.comments-toggle {
|
||||
padding: 0.75rem 1.5rem;
|
||||
padding: 0.7rem 1.25rem;
|
||||
background: var(--cds-button-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.comments-toggle:hover { background: var(--cds-hover-primary); }
|
||||
|
||||
|
||||
.comments-panel {
|
||||
display: none;
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: var(--cds-ui-01);
|
||||
border-radius: 3px;
|
||||
background: var(--cds-layer-accent);
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
}
|
||||
|
||||
|
||||
.comments-panel.open { display: block; }
|
||||
|
||||
|
||||
.comments-panel input,
|
||||
.comments-panel textarea {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
border-radius: 3px;
|
||||
background: var(--cds-ui-02);
|
||||
padding: 0.7rem;
|
||||
border: 1px solid var(--cds-border-strong);
|
||||
background: var(--cds-field);
|
||||
color: var(--cds-text-primary);
|
||||
font-family: inherit;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.comments-panel input:focus,
|
||||
.comments-panel textarea:focus { outline: 2px solid var(--cds-focus); outline-offset: -2px; }
|
||||
|
||||
.comments-panel textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
|
||||
.comment-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.comment-buttons button {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.submit-btn {
|
||||
background: var(--cds-button-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.submit-btn:hover { background: var(--cds-hover-primary); }
|
||||
|
||||
|
||||
.close-btn {
|
||||
background: var(--cds-border-subtle);
|
||||
background: var(--cds-layer-selected);
|
||||
color: var(--cds-text-primary);
|
||||
border: 1px solid var(--cds-border-strong);
|
||||
}
|
||||
|
||||
.close-btn:hover { background: var(--cds-hover-ui); }
|
||||
|
||||
|
||||
.close-btn:hover { background: var(--cds-layer-selected-hover); }
|
||||
|
||||
.comments-list {
|
||||
margin-top: 1rem;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
.comment-item {
|
||||
padding: 0.75rem;
|
||||
background: var(--cds-background);
|
||||
border: 1px solid var(--cds-border-subtle);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -353,22 +283,20 @@
|
||||
.proj-loading { color: var(--cds-text-secondary); font-style: italic; font-size: 13px; }
|
||||
.proj-row { display: flex; gap: 0.75rem; flex-wrap: wrap; align-items: center; }
|
||||
.proj-row select { flex: 1; min-width: 240px; padding: 0.6rem 0.7rem; font-size: 14px;
|
||||
border: 1px solid var(--cds-border-strong, #8d8d8d); border-radius: 4px; background: #fff; }
|
||||
border: 1px solid var(--cds-border-strong, #8d8d8d); background: #fff; }
|
||||
.proj-empty { background: var(--cds-ui-01, #fff); border: 1px dashed var(--cds-border-strong, #8d8d8d);
|
||||
border-radius: 6px; padding: 1.25rem; }
|
||||
padding: 1.25rem; }
|
||||
.proj-empty p { margin: 0 0 0.9rem; color: var(--cds-text-secondary); }
|
||||
.proj-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
|
||||
.proj-form { margin-top: 1rem; padding: 1rem; border: 1px solid var(--cds-ui-03, #e0e0e0); border-radius: 6px; background: var(--cds-ui-01, #fff); }
|
||||
.proj-form { margin-top: 1rem; padding: 1rem; border: 1px solid var(--cds-ui-03, #e0e0e0); background: var(--cds-ui-01, #fff); }
|
||||
.proj-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.75rem; margin-bottom: 0.9rem; }
|
||||
.proj-form-grid label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 12px; font-weight: 600; color: var(--cds-text-secondary); }
|
||||
.proj-form-grid input { padding: 0.55rem 0.65rem; font-size: 14px; border: 1px solid var(--cds-border-strong, #8d8d8d); border-radius: 4px; }
|
||||
.proj-form-grid input { padding: 0.55rem 0.65rem; font-size: 14px; border: 1px solid var(--cds-border-strong, #8d8d8d); }
|
||||
.proj-active { margin-top: 0.85rem; font-size: 13px; color: var(--cds-text-primary); }
|
||||
.link-like { background: none; border: none; color: var(--cds-link-01, #0f62fe); cursor: pointer; font-size: 13px; padding: 0; text-decoration: underline; }
|
||||
|
||||
/* RESPONSIVE */
|
||||
@media (max-width: 768px) {
|
||||
.header-content { flex-direction: column; text-align: center; }
|
||||
.header-spacer { display: none; }
|
||||
.hero h1 { font-size: 1.75rem; }
|
||||
.cards-grid { grid-template-columns: 1fr; }
|
||||
.container { padding: 1.5rem; }
|
||||
@@ -377,19 +305,17 @@
|
||||
</head>
|
||||
<body>
|
||||
<!-- HEADER -->
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<a href="index.html" class="logo">
|
||||
<img src="prime-controls-logo.jpg" alt="Prime Controls">
|
||||
<div>Work Package Suite</div>
|
||||
</a>
|
||||
<div class="header-spacer"></div>
|
||||
<nav class="header-nav">
|
||||
<a href="#overview">Overview</a>
|
||||
<a href="#comments">Feedback</a>
|
||||
<a href="#" onclick="openHelp();return false;">Help</a>
|
||||
</nav>
|
||||
</div>
|
||||
<header class="wp-appbar">
|
||||
<a href="index.html" class="wp-appbar-brand" title="Work Package Suite home">
|
||||
<span class="wp-logo-chip"><img src="prime-controls-logo.jpg" alt="Prime Controls"></span>
|
||||
<span class="wp-appbar-title">Work Package Suite</span>
|
||||
</a>
|
||||
<div class="wp-appbar-spacer"></div>
|
||||
<nav class="wp-appbar-actions">
|
||||
<a class="wp-appbar-link" href="#overview">Overview</a>
|
||||
<a class="wp-appbar-link" href="#comments">Feedback</a>
|
||||
<a class="wp-appbar-link" href="#" onclick="openHelp();return false;">Help</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
@@ -432,17 +358,13 @@
|
||||
<button class="card-button" id="card-dash-btn">Open Dashboard</button>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<!-- FIELD VIEW -->
|
||||
<a href="field.html" class="card" id="card-field">
|
||||
<h3>Field View</h3>
|
||||
<p>A phone-friendly view for the work face — update status, clear constraints, and log photos and notes. Installable to a home screen; works offline and syncs when you're back on network.</p>
|
||||
<button class="card-button" id="card-field-btn">Open Field View</button>
|
||||
</a>
|
||||
|
||||
<!-- QUICK START -->
|
||||
<div class="section quick-start">
|
||||
<h2>Getting Started</h2>
|
||||
<ol>
|
||||
<li><strong>Open "SOP Configuration"</strong> and complete the 10 steps for your project (~15 minutes)</li>
|
||||
<li><strong>Finish the SOP</strong> — this card turns green and unlocks the Work Package Creator</li>
|
||||
<li><strong>Open "Work Package Creator"</strong> to author Work Packages with your SOP defaults pre-populated</li>
|
||||
<li><strong>Leave feedback</strong> on any page using the feedback button below</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- COMMENTS SECTION -->
|
||||
@@ -588,6 +510,7 @@
|
||||
setHref('card-sop', 'work-package-suite.html?tab=sop');
|
||||
setHref('card-wp', 'work-package-suite.html?tab=wp');
|
||||
setHref('card-dash', 'work-package-suite.html?view=dashboard');
|
||||
setHref('card-field', 'field.html?src=home');
|
||||
|
||||
cards.style.display = '';
|
||||
heroTitle.textContent = active.name || 'Work Package Suite';
|
||||
|
||||
Reference in New Issue
Block a user