- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
679 lines
18 KiB
HTML
679 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Tractatus Credential Vault</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔐</text></svg>">
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1000px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.vault-header {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.vault-header h1 {
|
||
font-size: 24px;
|
||
color: #333;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.lock-status {
|
||
font-size: 14px;
|
||
padding: 8px 15px;
|
||
border-radius: 20px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.lock-status.locked {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
.lock-status.unlocked {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.btn-lock {
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-lock:hover:not(:disabled) {
|
||
background: #5a6268;
|
||
}
|
||
|
||
.btn-lock:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.unlock-section, .credentials-section {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.unlock-card h2 {
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
.unlock-card p {
|
||
margin-bottom: 20px;
|
||
color: #666;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.form-group input {
|
||
width: 100%;
|
||
padding: 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #667eea;
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 30px;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-primary:hover:not(:disabled) {
|
||
background: #5568d3;
|
||
}
|
||
|
||
.btn-primary:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.warning {
|
||
background: #fff3cd;
|
||
border: 1px solid #ffc107;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
margin-top: 20px;
|
||
color: #856404;
|
||
}
|
||
|
||
.error {
|
||
background: #f8d7da;
|
||
border: 1px solid #f5c6cb;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
margin-top: 20px;
|
||
color: #721c24;
|
||
}
|
||
|
||
.success {
|
||
background: #d4edda;
|
||
border: 1px solid #c3e6cb;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
margin-top: 20px;
|
||
color: #155724;
|
||
}
|
||
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
|
||
.credentials-section h2 {
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.credential-card {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.credential-card:hover {
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.credential-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #dee2e6;
|
||
}
|
||
|
||
.credential-header h3 {
|
||
font-size: 18px;
|
||
color: #333;
|
||
}
|
||
|
||
.credential-type {
|
||
background: #667eea;
|
||
color: white;
|
||
padding: 5px 15px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.credential-body {
|
||
display: none;
|
||
}
|
||
|
||
.credential-body.visible {
|
||
display: block;
|
||
}
|
||
|
||
.credential-field {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.credential-field label {
|
||
font-weight: 600;
|
||
color: #666;
|
||
min-width: 120px;
|
||
}
|
||
|
||
.credential-field span {
|
||
color: #333;
|
||
flex: 1;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.credential-field.password span {
|
||
font-family: monospace;
|
||
background: #e9ecef;
|
||
padding: 5px 10px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.credential-actions {
|
||
margin-top: 15px;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.btn-copy {
|
||
background: #28a745;
|
||
color: white;
|
||
border: none;
|
||
padding: 8px 15px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.btn-copy:hover {
|
||
background: #218838;
|
||
}
|
||
|
||
.totp-code {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
letter-spacing: 5px;
|
||
font-family: monospace;
|
||
color: #667eea;
|
||
padding: 15px;
|
||
background: #e9ecef;
|
||
border-radius: 5px;
|
||
text-align: center;
|
||
margin: 10px 0;
|
||
}
|
||
|
||
.totp-timer {
|
||
text-align: center;
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #666;
|
||
}
|
||
|
||
.spinner {
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #667eea;
|
||
border-radius: 50%;
|
||
width: 40px;
|
||
height: 40px;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 20px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.connection-status {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 10px 20px;
|
||
border-radius: 5px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.connection-status.connected {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.connection-status.disconnected {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.vault-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 15px;
|
||
}
|
||
|
||
.credential-field {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.credential-field label {
|
||
min-width: auto;
|
||
}
|
||
}
|
||
|
||
/* Section header with button */
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.section-header h2 {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* Project selector group */
|
||
.project-selector-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.project-selector-group label {
|
||
font-weight: 600;
|
||
color: #333;
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* Credential group headers */
|
||
.credential-group-header {
|
||
margin-top: 30px;
|
||
margin-bottom: 15px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid #e9ecef;
|
||
}
|
||
|
||
.credential-group-header:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.credential-group-header h2 {
|
||
font-size: 20px;
|
||
color: #333;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.credential-group-header .count {
|
||
font-size: 14px;
|
||
color: #999;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.credential-group {
|
||
display: grid;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.project-selector {
|
||
padding: 8px 15px;
|
||
border: 2px solid #667eea;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
background: white;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.project-selector:hover {
|
||
border-color: #5568d3;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.project-selector:focus {
|
||
outline: none;
|
||
border-color: #5568d3;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
/* Primary button */
|
||
.btn-primary {
|
||
background: #667eea;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #5568d3;
|
||
}
|
||
|
||
/* Secondary button */
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
/* Icon buttons */
|
||
.btn-icon {
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border-radius: 5px;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.btn-icon:hover {
|
||
background: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.btn-icon.btn-danger:hover {
|
||
background: rgba(220, 53, 69, 0.1);
|
||
}
|
||
|
||
/* Modal */
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
z-index: 1000;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
overflow: auto;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
margin: 5% auto;
|
||
padding: 0;
|
||
border-radius: 10px;
|
||
max-width: 600px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||
animation: modalFadeIn 0.3s;
|
||
}
|
||
|
||
@keyframes modalFadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-50px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20px;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.modal-header h3 {
|
||
margin: 0;
|
||
color: #333;
|
||
}
|
||
|
||
.modal-header .close {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
transition: color 0.2s;
|
||
}
|
||
|
||
.modal-header .close:hover {
|
||
color: #000;
|
||
}
|
||
|
||
.modal-content form {
|
||
padding: 20px;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
color: #333;
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.form-group input,
|
||
.form-group textarea {
|
||
width: 100%;
|
||
padding: 10px;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.form-group input:focus,
|
||
.form-group textarea:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||
}
|
||
|
||
.form-group textarea {
|
||
resize: vertical;
|
||
}
|
||
|
||
.modal-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
padding-top: 20px;
|
||
border-top: 1px solid #dee2e6;
|
||
}
|
||
|
||
.credential-header .credential-actions {
|
||
display: flex;
|
||
gap: 5px;
|
||
border: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="connection-status" class="connection-status disconnected">⚠️ Disconnected</div>
|
||
|
||
<div class="container">
|
||
<header class="vault-header">
|
||
<h1>🔐 Tractatus Credential Vault</h1>
|
||
<div class="header-actions">
|
||
<span id="lock-status" class="lock-status locked">🔒 Locked</span>
|
||
<button id="lock-btn" class="btn-lock" disabled>Lock</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div id="unlock-section" class="unlock-section">
|
||
<div class="unlock-card">
|
||
<h2>Unlock Vault</h2>
|
||
<p>Enter your master password to access credentials</p>
|
||
<form id="unlock-form">
|
||
<div class="form-group">
|
||
<label for="master-password">Master Password:</label>
|
||
<input type="password" id="master-password" name="master-password" required autocomplete="current-password" autofocus>
|
||
</div>
|
||
<button type="submit" class="btn-primary" id="unlock-btn">Unlock Vault</button>
|
||
</form>
|
||
<div id="unlock-message"></div>
|
||
<p class="warning">⚠️ Vault will auto-lock after 15 minutes of inactivity</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="credentials-section" class="credentials-section hidden">
|
||
<div class="section-header">
|
||
<div class="project-selector-group">
|
||
<label for="project-selector">Project:</label>
|
||
<select id="project-selector" class="project-selector">
|
||
<option value="tractatus">Tractatus</option>
|
||
<option value="family-history">Family History</option>
|
||
<option value="sydigital">SY Digital</option>
|
||
<option value="passport-consolidated">Passport Consolidated</option>
|
||
<option value="shared">Shared</option>
|
||
</select>
|
||
</div>
|
||
<button id="add-credential-btn" class="btn-primary">➕ Add Credential</button>
|
||
</div>
|
||
<div id="credentials-list">
|
||
<div class="loading">
|
||
<div class="spinner"></div>
|
||
<p>Loading credentials...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add/Edit Credential Modal -->
|
||
<div id="credential-modal" class="modal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3 id="modal-title">➕ Add Credential</h3>
|
||
<span id="modal-close-btn" class="close">×</span>
|
||
</div>
|
||
<form id="credential-form">
|
||
<div class="form-group">
|
||
<label for="cred-name">Name *</label>
|
||
<input type="text" id="cred-name" placeholder="e.g., Anthropic_API_Key" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cred-username">Username</label>
|
||
<input type="text" id="cred-username" placeholder="e.g., service-account">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cred-password">Password / Secret *</label>
|
||
<input type="password" id="cred-password" placeholder="Enter password or API key" autocomplete="new-password">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cred-url">URL</label>
|
||
<input type="url" id="cred-url" placeholder="https://console.anthropic.com">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="cred-notes">Notes</label>
|
||
<textarea id="cred-notes" rows="3" placeholder="Additional notes..."></textarea>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button type="button" id="modal-cancel-btn" class="btn-secondary">Cancel</button>
|
||
<button type="button" id="modal-save-btn" class="btn-primary">Save</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="vault-ui.js"></script>
|
||
</body>
|
||
</html>
|