Add comprehensive Docker deployment and file upload functionality
Features Added: • Docker containerization with multi-stage Python 3.12 build • Caddy reverse proxy integration with automatic SSL • File upload interface for .claude.json imports with preview • Comprehensive hook system with 39+ hook types across 9 categories • Complete documentation system with Docker and import guides Technical Improvements: • Enhanced database models with hook tracking capabilities • Robust file validation and error handling for uploads • Production-ready Docker compose configuration • Health checks and resource limits for containers • Database initialization scripts for containerized deployments Documentation: • Docker Deployment Guide with troubleshooting • Data Import Guide with step-by-step instructions • Updated Getting Started guide with new features • Enhanced documentation index with responsive grid layout 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bec1606c86
commit
50c80596d0
36 changed files with 4334 additions and 172 deletions
|
|
@ -29,28 +29,79 @@
|
|||
This will create projects and estimate sessions based on your past usage.
|
||||
</p>
|
||||
|
||||
<!-- Import Form -->
|
||||
<form id="import-form">
|
||||
<div class="mb-3">
|
||||
<label for="file-path" class="form-label">File Path (optional)</label>
|
||||
<input type="text" class="form-control" id="file-path"
|
||||
placeholder="Leave empty to use ~/.claude.json">
|
||||
<div class="form-text">
|
||||
If left empty, will try to import from the default location: <code>~/.claude.json</code>
|
||||
<!-- Import Method Tabs -->
|
||||
<ul class="nav nav-pills mb-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="upload-tab" data-bs-toggle="pill"
|
||||
data-bs-target="#upload-panel" type="button" role="tab">
|
||||
<i class="fas fa-cloud-upload-alt me-1"></i>
|
||||
Upload File
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="path-tab" data-bs-toggle="pill"
|
||||
data-bs-target="#path-panel" type="button" role="tab">
|
||||
<i class="fas fa-folder-open me-1"></i>
|
||||
File Path
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<!-- Upload File Panel -->
|
||||
<div class="tab-pane fade show active" id="upload-panel" role="tabpanel">
|
||||
<div class="mb-3">
|
||||
<label for="file-upload" class="form-label">Select .claude.json file</label>
|
||||
<input type="file" class="form-control" id="file-upload"
|
||||
accept=".json" onchange="handleFileSelect(event)">
|
||||
<div class="form-text">
|
||||
Upload your <code>.claude.json</code> file directly from your computer
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="file-info" class="alert alert-info" style="display: none;">
|
||||
<div id="file-details"></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="previewUpload()"
|
||||
id="preview-upload-btn" disabled>
|
||||
<i class="fas fa-eye me-1"></i>
|
||||
Preview Upload
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="runUpload()"
|
||||
id="import-upload-btn" disabled>
|
||||
<i class="fas fa-cloud-upload-alt me-1"></i>
|
||||
Import Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="previewImport()">
|
||||
<i class="fas fa-eye me-1"></i>
|
||||
Preview Import
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="runImport()">
|
||||
<i class="fas fa-download me-1"></i>
|
||||
Import Data
|
||||
</button>
|
||||
|
||||
<!-- File Path Panel -->
|
||||
<div class="tab-pane fade" id="path-panel" role="tabpanel">
|
||||
<form id="import-form">
|
||||
<div class="mb-3">
|
||||
<label for="file-path" class="form-label">File Path (optional)</label>
|
||||
<input type="text" class="form-control" id="file-path"
|
||||
placeholder="Leave empty to use ~/.claude.json">
|
||||
<div class="form-text">
|
||||
If left empty, will try to import from the default location: <code>~/.claude.json</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="previewImport()">
|
||||
<i class="fas fa-eye me-1"></i>
|
||||
Preview Import
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="runImport()">
|
||||
<i class="fas fa-download me-1"></i>
|
||||
Import Data
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div id="import-results" class="mt-4" style="display: none;">
|
||||
|
|
@ -122,6 +173,142 @@
|
|||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let selectedFile = null;
|
||||
|
||||
function handleFileSelect(event) {
|
||||
const file = event.target.files[0];
|
||||
selectedFile = file;
|
||||
|
||||
if (file) {
|
||||
// Validate file type
|
||||
if (!file.name.endsWith('.json')) {
|
||||
showError('Please select a JSON file (.json)');
|
||||
resetFileUpload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check file size (10MB limit)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
showError('File too large. Maximum size is 10MB.');
|
||||
resetFileUpload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show file info
|
||||
const fileInfo = document.getElementById('file-info');
|
||||
const fileDetails = document.getElementById('file-details');
|
||||
|
||||
fileDetails.innerHTML = `
|
||||
<strong>Selected file:</strong> ${file.name}<br>
|
||||
<strong>Size:</strong> ${(file.size / 1024).toFixed(1)} KB<br>
|
||||
<strong>Modified:</strong> ${new Date(file.lastModified).toLocaleString()}
|
||||
`;
|
||||
|
||||
fileInfo.style.display = 'block';
|
||||
|
||||
// Enable buttons
|
||||
document.getElementById('preview-upload-btn').disabled = false;
|
||||
document.getElementById('import-upload-btn').disabled = false;
|
||||
} else {
|
||||
resetFileUpload();
|
||||
}
|
||||
}
|
||||
|
||||
function resetFileUpload() {
|
||||
selectedFile = null;
|
||||
document.getElementById('file-info').style.display = 'none';
|
||||
document.getElementById('preview-upload-btn').disabled = true;
|
||||
document.getElementById('import-upload-btn').disabled = true;
|
||||
}
|
||||
|
||||
async function previewUpload() {
|
||||
if (!selectedFile) {
|
||||
showError('Please select a file first');
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsDiv = document.getElementById('import-results');
|
||||
const resultsContent = document.getElementById('results-content');
|
||||
|
||||
// Show loading
|
||||
resultsContent.innerHTML = `
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Previewing...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">Analyzing uploaded file...</p>
|
||||
</div>
|
||||
`;
|
||||
resultsDiv.style.display = 'block';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
|
||||
const response = await fetch('/api/import/claude-json/preview-upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showPreviewResults(data, true); // Pass true to indicate this is an upload
|
||||
} else {
|
||||
showError(data.detail || 'Preview failed');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Network error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function runUpload() {
|
||||
if (!selectedFile) {
|
||||
showError('Please select a file first');
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm import
|
||||
if (!confirm('This will import data into your tracker database. Continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultsDiv = document.getElementById('import-results');
|
||||
const resultsContent = document.getElementById('results-content');
|
||||
|
||||
// Show loading
|
||||
resultsContent.innerHTML = `
|
||||
<div class="text-center py-3">
|
||||
<div class="spinner-border text-success" role="status">
|
||||
<span class="visually-hidden">Importing...</span>
|
||||
</div>
|
||||
<p class="mt-2 text-muted">Importing uploaded file...</p>
|
||||
</div>
|
||||
`;
|
||||
resultsDiv.style.display = 'block';
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile);
|
||||
|
||||
const response = await fetch('/api/import/claude-json/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showImportResults(data, true); // Pass true to indicate this is an upload
|
||||
} else {
|
||||
showError(data.detail || 'Import failed');
|
||||
}
|
||||
} catch (error) {
|
||||
showError('Network error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function previewImport() {
|
||||
const filePath = document.getElementById('file-path').value.trim();
|
||||
const resultsDiv = document.getElementById('import-results');
|
||||
|
|
@ -204,14 +391,14 @@ async function runImport() {
|
|||
}
|
||||
}
|
||||
|
||||
function showPreviewResults(data) {
|
||||
function showPreviewResults(data, isUpload = false) {
|
||||
const html = `
|
||||
<div class="alert alert-info">
|
||||
<h6><i class="fas fa-eye me-1"></i> Import Preview</h6>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><strong>File:</strong> <code>${data.file_path}</code></p>
|
||||
<p><strong>File:</strong> <code>${isUpload ? data.file_name : data.file_path}</code></p>
|
||||
<p><strong>Size:</strong> ${data.file_size_mb} MB</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
|
@ -241,7 +428,7 @@ function showPreviewResults(data) {
|
|||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button class="btn btn-success" onclick="runImport()">
|
||||
<button class="btn btn-success" onclick="${isUpload ? 'runUpload()' : 'runImport()'}">
|
||||
<i class="fas fa-check me-1"></i>
|
||||
Looks good - Import this data
|
||||
</button>
|
||||
|
|
@ -251,13 +438,18 @@ function showPreviewResults(data) {
|
|||
document.getElementById('results-content').innerHTML = html;
|
||||
}
|
||||
|
||||
function showImportResults(data) {
|
||||
function showImportResults(data, isUpload = false) {
|
||||
const results = data.results;
|
||||
const hasErrors = results.errors && results.errors.length > 0;
|
||||
|
||||
let successMessage = '<h6><i class="fas fa-check me-1"></i> Import Completed Successfully!</h6>';
|
||||
if (isUpload) {
|
||||
successMessage += `<p class="mb-0"><strong>File:</strong> ${data.file_name} (${data.file_size_kb} KB)</p>`;
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="alert alert-success">
|
||||
<h6><i class="fas fa-check me-1"></i> Import Completed Successfully!</h6>
|
||||
${successMessage}
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue