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
|
|
@ -11,7 +11,7 @@ from datetime import datetime, timedelta
|
|||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
|
|
@ -40,6 +40,19 @@ class ClaudeJsonImporter:
|
|||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
|
||||
|
||||
return await self._import_claude_data(claude_data)
|
||||
|
||||
async def import_from_content(self, content: str) -> Dict[str, Any]:
|
||||
"""Import data from .claude.json file content."""
|
||||
try:
|
||||
claude_data = json.loads(content)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
|
||||
|
||||
return await self._import_claude_data(claude_data)
|
||||
|
||||
async def _import_claude_data(self, claude_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Common import logic for both file and content imports."""
|
||||
results = {
|
||||
"projects_imported": 0,
|
||||
"sessions_estimated": 0,
|
||||
|
|
@ -337,6 +350,64 @@ async def import_claude_json(
|
|||
)
|
||||
|
||||
|
||||
@router.post("/import/claude-json/upload")
|
||||
async def import_claude_json_upload(
|
||||
file: UploadFile = File(...),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Import data from uploaded .claude.json file.
|
||||
"""
|
||||
# Validate file type
|
||||
if file.filename and not file.filename.endswith('.json'):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be a JSON file (.json)"
|
||||
)
|
||||
|
||||
# Check file size (limit to 10MB)
|
||||
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
||||
content = await file.read()
|
||||
|
||||
if len(content) > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail="File too large. Maximum size is 10MB."
|
||||
)
|
||||
|
||||
try:
|
||||
# Decode file content
|
||||
file_content = content.decode('utf-8')
|
||||
|
||||
# Import data
|
||||
importer = ClaudeJsonImporter(db)
|
||||
results = await importer.import_from_content(file_content)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Import completed successfully",
|
||||
"file_name": file.filename,
|
||||
"file_size_kb": round(len(content) / 1024, 2),
|
||||
"results": results
|
||||
}
|
||||
|
||||
except UnicodeDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be UTF-8 encoded"
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid file format: {e}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Import failed: {e}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/import/claude-json/preview")
|
||||
async def preview_claude_json_import(
|
||||
file_path: Optional[str] = None
|
||||
|
|
@ -379,6 +450,73 @@ async def preview_claude_json_import(
|
|||
"history_entries": 0
|
||||
}
|
||||
|
||||
# Count total history entries across all projects
|
||||
if "projects" in claude_data:
|
||||
total_history = sum(
|
||||
len(proj.get("history", []))
|
||||
for proj in claude_data["projects"].values()
|
||||
)
|
||||
preview["history_entries"] = total_history
|
||||
|
||||
return preview
|
||||
|
||||
|
||||
@router.post("/import/claude-json/preview-upload")
|
||||
async def preview_claude_json_upload(
|
||||
file: UploadFile = File(...)
|
||||
):
|
||||
"""
|
||||
Preview what would be imported from uploaded .claude.json file without actually importing.
|
||||
"""
|
||||
# Validate file type
|
||||
if file.filename and not file.filename.endswith('.json'):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be a JSON file (.json)"
|
||||
)
|
||||
|
||||
# Check file size (limit to 10MB)
|
||||
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
|
||||
content = await file.read()
|
||||
|
||||
if len(content) > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail="File too large. Maximum size is 10MB."
|
||||
)
|
||||
|
||||
try:
|
||||
# Decode and parse file content
|
||||
file_content = content.decode('utf-8')
|
||||
claude_data = json.loads(file_content)
|
||||
except UnicodeDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be UTF-8 encoded"
|
||||
)
|
||||
except json.JSONDecodeError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Invalid JSON in Claude configuration file: {e}"
|
||||
)
|
||||
|
||||
preview = {
|
||||
"file_name": file.filename,
|
||||
"file_size_mb": round(len(content) / (1024 * 1024), 2),
|
||||
"file_size_kb": round(len(content) / 1024, 2),
|
||||
"claude_usage": {
|
||||
"num_startups": claude_data.get("numStartups", 0),
|
||||
"first_start_time": claude_data.get("firstStartTime"),
|
||||
"prompt_queue_use_count": claude_data.get("promptQueueUseCount", 0)
|
||||
},
|
||||
"projects": {
|
||||
"total_count": len(claude_data.get("projects", {})),
|
||||
"paths": list(claude_data.get("projects", {}).keys())[:10], # Show first 10
|
||||
"has_more": len(claude_data.get("projects", {})) > 10
|
||||
},
|
||||
"history_entries": 0
|
||||
}
|
||||
|
||||
# Count total history entries across all projects
|
||||
if "projects" in claude_data:
|
||||
total_history = sum(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue