tractatus/.credential-vault/index.html
TheFlow ac2db33732 fix(submissions): restructure Economist package and fix article display
- 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>
2025-10-24 08:47:42 +13:00

679 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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">&times;</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>