Add comprehensive documentation system and tool call tracking
## Documentation System - Create complete documentation hub at /dashboard/docs with: - Getting Started guide with quick setup and troubleshooting - Hook Setup Guide with platform-specific configurations - API Reference with all endpoints and examples - FAQ with searchable questions and categories - Add responsive design with interactive features - Update navigation in base template ## Tool Call Tracking - Add ToolCall model for tracking Claude Code tool usage - Create /api/tool-calls endpoints for recording and analytics - Add tool_call hook type with auto-session detection - Include tool calls in project statistics and recalculation - Track tool names, parameters, execution time, and success rates ## Project Enhancements - Add project timeline and statistics pages (fix 404 errors) - Create recalculation script for fixing zero statistics - Update project stats to include tool call counts - Enhance session model with tool call relationships ## Infrastructure - Switch from requirements.txt to pyproject.toml/uv.lock - Add data import functionality for claude.json files - Update database connection to include all new models - Add comprehensive API documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
166247bf70
commit
bec1606c86
@ -83,6 +83,54 @@ async def log_conversation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/conversations", response_model=List[ConversationSearchResult])
|
||||||
|
async def get_conversations(
|
||||||
|
project_id: Optional[int] = Query(None, description="Filter by project ID"),
|
||||||
|
limit: int = Query(50, description="Maximum number of results"),
|
||||||
|
offset: int = Query(0, description="Number of results to skip"),
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Get recent conversations with optional project filtering.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Build query
|
||||||
|
query = select(Conversation).options(
|
||||||
|
selectinload(Conversation.session).selectinload(Session.project)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add project filter if specified
|
||||||
|
if project_id:
|
||||||
|
query = query.join(Session).where(Session.project_id == project_id)
|
||||||
|
|
||||||
|
# Order by timestamp descending, add pagination
|
||||||
|
query = query.order_by(Conversation.timestamp.desc()).offset(offset).limit(limit)
|
||||||
|
|
||||||
|
result = await db.execute(query)
|
||||||
|
conversations = result.scalars().all()
|
||||||
|
|
||||||
|
# Convert to response format
|
||||||
|
results = []
|
||||||
|
for conversation in conversations:
|
||||||
|
results.append(ConversationSearchResult(
|
||||||
|
id=conversation.id,
|
||||||
|
project_name=conversation.session.project.name,
|
||||||
|
timestamp=conversation.timestamp,
|
||||||
|
user_prompt=conversation.user_prompt,
|
||||||
|
claude_response=conversation.claude_response,
|
||||||
|
relevance_score=1.0, # All results are equally relevant when just listing
|
||||||
|
context=[] # No context snippets needed for listing
|
||||||
|
))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to get conversations: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/conversations/search", response_model=List[ConversationSearchResult])
|
@router.get("/conversations/search", response_model=List[ConversationSearchResult])
|
||||||
async def search_conversations(
|
async def search_conversations(
|
||||||
query: str = Query(..., description="Search query"),
|
query: str = Query(..., description="Search query"),
|
||||||
|
390
app/api/importer.py
Normal file
390
app/api/importer.py
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
"""
|
||||||
|
Data importer for Claude Code .claude.json file.
|
||||||
|
|
||||||
|
This module provides functionality to import historical data from the
|
||||||
|
.claude.json configuration file into the project tracker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from app.database.connection import get_db
|
||||||
|
from app.models.project import Project
|
||||||
|
from app.models.session import Session
|
||||||
|
from app.models.conversation import Conversation
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class ClaudeJsonImporter:
|
||||||
|
"""Importer for .claude.json data."""
|
||||||
|
|
||||||
|
def __init__(self, db: AsyncSession):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def import_from_file(self, file_path: str) -> Dict[str, Any]:
|
||||||
|
"""Import data from .claude.json file."""
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise FileNotFoundError(f"Claude configuration file not found: {file_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
claude_data = json.load(f)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
|
||||||
|
|
||||||
|
results = {
|
||||||
|
"projects_imported": 0,
|
||||||
|
"sessions_estimated": 0,
|
||||||
|
"conversations_imported": 0,
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Import basic usage statistics
|
||||||
|
await self._import_usage_stats(claude_data, results)
|
||||||
|
|
||||||
|
# Import projects and their history
|
||||||
|
if "projects" in claude_data:
|
||||||
|
await self._import_projects(claude_data["projects"], results)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
async def _import_usage_stats(self, claude_data: Dict[str, Any], results: Dict[str, Any]):
|
||||||
|
"""Import basic usage statistics."""
|
||||||
|
# We could create a synthetic "Claude Code Usage" project to track overall stats
|
||||||
|
if claude_data.get("numStartups") and claude_data.get("firstStartTime"):
|
||||||
|
try:
|
||||||
|
first_start = datetime.fromisoformat(
|
||||||
|
claude_data["firstStartTime"].replace('Z', '+00:00')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a synthetic project for overall Claude Code usage
|
||||||
|
usage_project = await self._get_or_create_project(
|
||||||
|
name="Claude Code Usage Statistics",
|
||||||
|
path="<system>",
|
||||||
|
description="Imported usage statistics from .claude.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Estimate session distribution over time
|
||||||
|
num_startups = claude_data["numStartups"]
|
||||||
|
days_since_first = (datetime.now() - first_start.replace(tzinfo=None)).days
|
||||||
|
|
||||||
|
if days_since_first > 0:
|
||||||
|
# Create estimated sessions spread over the usage period
|
||||||
|
await self._create_estimated_sessions(
|
||||||
|
usage_project,
|
||||||
|
first_start.replace(tzinfo=None),
|
||||||
|
num_startups,
|
||||||
|
days_since_first
|
||||||
|
)
|
||||||
|
results["sessions_estimated"] = num_startups
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results["errors"].append(f"Failed to import usage stats: {e}")
|
||||||
|
|
||||||
|
async def _import_projects(self, projects_data: Dict[str, Any], results: Dict[str, Any]):
|
||||||
|
"""Import project data from .claude.json."""
|
||||||
|
for project_path, project_info in projects_data.items():
|
||||||
|
try:
|
||||||
|
# Skip system paths or non-meaningful paths
|
||||||
|
if project_path in ["<system>", "/", "/tmp"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract project name from path
|
||||||
|
project_name = Path(project_path).name or "Unknown Project"
|
||||||
|
|
||||||
|
# Create or get existing project
|
||||||
|
project = await self._get_or_create_project(
|
||||||
|
name=project_name,
|
||||||
|
path=project_path
|
||||||
|
)
|
||||||
|
|
||||||
|
results["projects_imported"] += 1
|
||||||
|
|
||||||
|
# Import conversation history if available
|
||||||
|
if "history" in project_info and isinstance(project_info["history"], list):
|
||||||
|
conversation_count = await self._import_project_history(
|
||||||
|
project,
|
||||||
|
project_info["history"]
|
||||||
|
)
|
||||||
|
results["conversations_imported"] += conversation_count
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
results["errors"].append(f"Failed to import project {project_path}: {e}")
|
||||||
|
|
||||||
|
async def _get_or_create_project(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
path: str,
|
||||||
|
description: Optional[str] = None
|
||||||
|
) -> Project:
|
||||||
|
"""Get existing project or create new one."""
|
||||||
|
# Check if project already exists
|
||||||
|
result = await self.db.execute(
|
||||||
|
select(Project).where(Project.path == path)
|
||||||
|
)
|
||||||
|
existing_project = result.scalars().first()
|
||||||
|
|
||||||
|
if existing_project:
|
||||||
|
return existing_project
|
||||||
|
|
||||||
|
# Try to detect languages from path
|
||||||
|
languages = self._detect_languages(path)
|
||||||
|
|
||||||
|
# Create new project
|
||||||
|
project = Project(
|
||||||
|
name=name,
|
||||||
|
path=path,
|
||||||
|
languages=languages
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.add(project)
|
||||||
|
await self.db.commit()
|
||||||
|
await self.db.refresh(project)
|
||||||
|
|
||||||
|
return project
|
||||||
|
|
||||||
|
def _detect_languages(self, project_path: str) -> Optional[List[str]]:
|
||||||
|
"""Attempt to detect programming languages from project directory."""
|
||||||
|
languages = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.exists(project_path) and os.path.isdir(project_path):
|
||||||
|
# Look for common files to infer languages
|
||||||
|
files = os.listdir(project_path)
|
||||||
|
|
||||||
|
# Python
|
||||||
|
if any(f.endswith(('.py', '.pyx', '.pyi')) for f in files) or 'requirements.txt' in files:
|
||||||
|
languages.append('python')
|
||||||
|
|
||||||
|
# JavaScript/TypeScript
|
||||||
|
if any(f.endswith(('.js', '.jsx', '.ts', '.tsx')) for f in files) or 'package.json' in files:
|
||||||
|
if any(f.endswith(('.ts', '.tsx')) for f in files):
|
||||||
|
languages.append('typescript')
|
||||||
|
else:
|
||||||
|
languages.append('javascript')
|
||||||
|
|
||||||
|
# Go
|
||||||
|
if any(f.endswith('.go') for f in files) or 'go.mod' in files:
|
||||||
|
languages.append('go')
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
if any(f.endswith('.rs') for f in files) or 'Cargo.toml' in files:
|
||||||
|
languages.append('rust')
|
||||||
|
|
||||||
|
# Java
|
||||||
|
if any(f.endswith('.java') for f in files) or 'pom.xml' in files:
|
||||||
|
languages.append('java')
|
||||||
|
|
||||||
|
except (OSError, PermissionError):
|
||||||
|
# If we can't read the directory, that's okay
|
||||||
|
pass
|
||||||
|
|
||||||
|
return languages if languages else None
|
||||||
|
|
||||||
|
async def _create_estimated_sessions(
|
||||||
|
self,
|
||||||
|
project: Project,
|
||||||
|
first_start: datetime,
|
||||||
|
num_startups: int,
|
||||||
|
days_since_first: int
|
||||||
|
):
|
||||||
|
"""Create estimated sessions based on startup count."""
|
||||||
|
# Check if we already have sessions for this project
|
||||||
|
existing_sessions = await self.db.execute(
|
||||||
|
select(Session).where(
|
||||||
|
Session.project_id == project.id,
|
||||||
|
Session.session_type == "startup"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if existing_sessions.scalars().first():
|
||||||
|
return # Sessions already exist, skip creation
|
||||||
|
|
||||||
|
# Don't create too many sessions - limit to reasonable estimates
|
||||||
|
max_sessions = min(num_startups, 50) # Cap at 50 sessions
|
||||||
|
|
||||||
|
# Distribute sessions over the time period
|
||||||
|
if days_since_first > 0:
|
||||||
|
sessions_per_day = max_sessions / days_since_first
|
||||||
|
|
||||||
|
for i in range(max_sessions):
|
||||||
|
# Spread sessions over the time period
|
||||||
|
days_offset = int(i / sessions_per_day) if sessions_per_day > 0 else i
|
||||||
|
session_time = first_start + timedelta(days=days_offset)
|
||||||
|
|
||||||
|
# Estimate session duration (30-180 minutes)
|
||||||
|
import random
|
||||||
|
duration = random.randint(30, 180)
|
||||||
|
|
||||||
|
session = Session(
|
||||||
|
project_id=project.id,
|
||||||
|
start_time=session_time,
|
||||||
|
end_time=session_time + timedelta(minutes=duration),
|
||||||
|
session_type="startup",
|
||||||
|
working_directory=project.path,
|
||||||
|
duration_minutes=duration,
|
||||||
|
activity_count=random.randint(5, 25), # Estimated activity
|
||||||
|
conversation_count=random.randint(2, 8) # Estimated conversations
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.add(session)
|
||||||
|
|
||||||
|
await self.db.commit()
|
||||||
|
|
||||||
|
async def _import_project_history(
|
||||||
|
self,
|
||||||
|
project: Project,
|
||||||
|
history: List[Dict[str, Any]]
|
||||||
|
) -> int:
|
||||||
|
"""Import conversation history for a project."""
|
||||||
|
# Check if we already have history conversations for this project
|
||||||
|
existing_conversations = await self.db.execute(
|
||||||
|
select(Conversation).where(
|
||||||
|
Conversation.context.like('%"imported_from": ".claude.json"%'),
|
||||||
|
Conversation.session.has(Session.project_id == project.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if existing_conversations.scalars().first():
|
||||||
|
return 0 # History already imported, skip
|
||||||
|
|
||||||
|
conversation_count = 0
|
||||||
|
|
||||||
|
# Create a synthetic session for imported history
|
||||||
|
history_session = Session(
|
||||||
|
project_id=project.id,
|
||||||
|
start_time=datetime.now() - timedelta(days=30), # Assume recent
|
||||||
|
session_type="history_import", # Different type to avoid conflicts
|
||||||
|
working_directory=project.path,
|
||||||
|
activity_count=len(history),
|
||||||
|
conversation_count=len(history)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.add(history_session)
|
||||||
|
await self.db.commit()
|
||||||
|
await self.db.refresh(history_session)
|
||||||
|
|
||||||
|
# Import each history entry as a conversation
|
||||||
|
for i, entry in enumerate(history[:20]): # Limit to 20 entries
|
||||||
|
try:
|
||||||
|
display_text = entry.get("display", "")
|
||||||
|
if display_text:
|
||||||
|
conversation = Conversation(
|
||||||
|
session_id=history_session.id,
|
||||||
|
timestamp=history_session.start_time + timedelta(minutes=i * 5),
|
||||||
|
user_prompt=display_text,
|
||||||
|
exchange_type="user_prompt",
|
||||||
|
context={"imported_from": ".claude.json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.add(conversation)
|
||||||
|
conversation_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
# Skip problematic entries
|
||||||
|
continue
|
||||||
|
|
||||||
|
if conversation_count > 0:
|
||||||
|
await self.db.commit()
|
||||||
|
|
||||||
|
return conversation_count
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/import/claude-json")
|
||||||
|
async def import_claude_json(
|
||||||
|
file_path: Optional[str] = None,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Import data from .claude.json file.
|
||||||
|
|
||||||
|
If no file_path is provided, tries to find .claude.json in the user's home directory.
|
||||||
|
"""
|
||||||
|
if not file_path:
|
||||||
|
# Try default location
|
||||||
|
home_path = Path.home() / ".claude.json"
|
||||||
|
file_path = str(home_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
importer = ClaudeJsonImporter(db)
|
||||||
|
results = await importer.import_from_file(file_path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Import completed successfully",
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Claude configuration file not found: {e}"
|
||||||
|
)
|
||||||
|
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
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Preview what would be imported from .claude.json file without actually importing.
|
||||||
|
"""
|
||||||
|
if not file_path:
|
||||||
|
home_path = Path.home() / ".claude.json"
|
||||||
|
file_path = str(home_path)
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Claude configuration file not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
claude_data = json.load(f)
|
||||||
|
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_path": file_path,
|
||||||
|
"file_size_mb": round(os.path.getsize(file_path) / (1024 * 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(
|
||||||
|
len(proj.get("history", []))
|
||||||
|
for proj in claude_data["projects"].values()
|
||||||
|
)
|
||||||
|
preview["history_entries"] = total_history
|
||||||
|
|
||||||
|
return preview
|
254
app/api/tool_calls.py
Normal file
254
app/api/tool_calls.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
"""
|
||||||
|
Tool call tracking API endpoints.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy import select, func, desc
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from app.database.connection import get_db
|
||||||
|
from app.models.session import Session
|
||||||
|
from app.models.tool_call import ToolCall
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCallRequest(BaseModel):
|
||||||
|
"""Request schema for recording a tool call."""
|
||||||
|
session_id: Optional[str] = Field(None, description="Session ID (auto-detected if not provided)")
|
||||||
|
tool_name: str = Field(..., description="Name of the tool being called")
|
||||||
|
parameters: Optional[dict] = Field(None, description="Tool parameters as JSON object")
|
||||||
|
result_status: Optional[str] = Field("success", description="Result status: success, error, timeout")
|
||||||
|
error_message: Optional[str] = Field(None, description="Error message if failed")
|
||||||
|
execution_time_ms: Optional[int] = Field(None, description="Execution time in milliseconds")
|
||||||
|
timestamp: Optional[datetime] = Field(None, description="Timestamp of the tool call")
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCallResponse(BaseModel):
|
||||||
|
"""Response schema for tool call operations."""
|
||||||
|
id: int
|
||||||
|
session_id: str
|
||||||
|
tool_name: str
|
||||||
|
result_status: str
|
||||||
|
timestamp: datetime
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
|
class ToolUsageStats(BaseModel):
|
||||||
|
"""Statistics about tool usage."""
|
||||||
|
tool_name: str
|
||||||
|
total_calls: int
|
||||||
|
success_calls: int
|
||||||
|
error_calls: int
|
||||||
|
avg_execution_time_ms: Optional[float]
|
||||||
|
success_rate: float
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_session_id() -> Optional[str]:
|
||||||
|
"""Get the current session ID from the temporary file."""
|
||||||
|
try:
|
||||||
|
with open("/tmp/claude-session-id", "r") as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except (OSError, FileNotFoundError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/tool-calls", response_model=ToolCallResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def record_tool_call(
|
||||||
|
request: ToolCallRequest,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Record a tool call made during a Claude Code session.
|
||||||
|
|
||||||
|
This endpoint is called by Claude Code hooks when tools are used.
|
||||||
|
It automatically detects the current session if not provided.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get session ID
|
||||||
|
session_id = request.session_id
|
||||||
|
if not session_id:
|
||||||
|
session_id = await get_current_session_id()
|
||||||
|
|
||||||
|
if not session_id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="No active session found. Please provide session_id or ensure a session is running."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify session exists
|
||||||
|
result = await db.execute(
|
||||||
|
select(Session).where(Session.id == int(session_id))
|
||||||
|
)
|
||||||
|
session = result.scalars().first()
|
||||||
|
|
||||||
|
if not session:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Session {session_id} not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create tool call record
|
||||||
|
tool_call = ToolCall(
|
||||||
|
session_id=str(session_id),
|
||||||
|
tool_name=request.tool_name,
|
||||||
|
parameters=json.dumps(request.parameters) if request.parameters else None,
|
||||||
|
result_status=request.result_status or "success",
|
||||||
|
error_message=request.error_message,
|
||||||
|
execution_time_ms=request.execution_time_ms,
|
||||||
|
timestamp=request.timestamp or datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(tool_call)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(tool_call)
|
||||||
|
|
||||||
|
return ToolCallResponse(
|
||||||
|
id=tool_call.id,
|
||||||
|
session_id=tool_call.session_id,
|
||||||
|
tool_name=tool_call.tool_name,
|
||||||
|
result_status=tool_call.result_status,
|
||||||
|
timestamp=tool_call.timestamp,
|
||||||
|
message=f"Tool call '{request.tool_name}' recorded successfully"
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to record tool call: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tool-calls/session/{session_id}", response_model=List[dict])
|
||||||
|
async def get_session_tool_calls(
|
||||||
|
session_id: str,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get all tool calls for a specific session."""
|
||||||
|
try:
|
||||||
|
result = await db.execute(
|
||||||
|
select(ToolCall)
|
||||||
|
.where(ToolCall.session_id == session_id)
|
||||||
|
.order_by(desc(ToolCall.timestamp))
|
||||||
|
)
|
||||||
|
tool_calls = result.scalars().all()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"id": tc.id,
|
||||||
|
"tool_name": tc.tool_name,
|
||||||
|
"parameters": json.loads(tc.parameters) if tc.parameters else None,
|
||||||
|
"result_status": tc.result_status,
|
||||||
|
"error_message": tc.error_message,
|
||||||
|
"execution_time_ms": tc.execution_time_ms,
|
||||||
|
"timestamp": tc.timestamp
|
||||||
|
}
|
||||||
|
for tc in tool_calls
|
||||||
|
]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to get tool calls: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tool-calls/stats", response_model=List[ToolUsageStats])
|
||||||
|
async def get_tool_usage_stats(
|
||||||
|
project_id: Optional[int] = None,
|
||||||
|
start_date: Optional[datetime] = None,
|
||||||
|
end_date: Optional[datetime] = None,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get tool usage statistics with optional filtering."""
|
||||||
|
try:
|
||||||
|
# Base query
|
||||||
|
query = select(
|
||||||
|
ToolCall.tool_name,
|
||||||
|
func.count(ToolCall.id).label("total_calls"),
|
||||||
|
func.sum(func.case([(ToolCall.result_status == "success", 1)], else_=0)).label("success_calls"),
|
||||||
|
func.sum(func.case([(ToolCall.result_status == "error", 1)], else_=0)).label("error_calls"),
|
||||||
|
func.avg(ToolCall.execution_time_ms).label("avg_execution_time_ms")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply filters
|
||||||
|
if project_id:
|
||||||
|
query = query.join(Session).where(Session.project_id == project_id)
|
||||||
|
|
||||||
|
if start_date:
|
||||||
|
query = query.where(ToolCall.timestamp >= start_date)
|
||||||
|
|
||||||
|
if end_date:
|
||||||
|
query = query.where(ToolCall.timestamp <= end_date)
|
||||||
|
|
||||||
|
# Group by tool name
|
||||||
|
query = query.group_by(ToolCall.tool_name).order_by(desc("total_calls"))
|
||||||
|
|
||||||
|
result = await db.execute(query)
|
||||||
|
rows = result.all()
|
||||||
|
|
||||||
|
stats = []
|
||||||
|
for row in rows:
|
||||||
|
success_rate = (row.success_calls / row.total_calls * 100) if row.total_calls > 0 else 0
|
||||||
|
|
||||||
|
stats.append(ToolUsageStats(
|
||||||
|
tool_name=row.tool_name,
|
||||||
|
total_calls=row.total_calls,
|
||||||
|
success_calls=row.success_calls,
|
||||||
|
error_calls=row.error_calls,
|
||||||
|
avg_execution_time_ms=float(row.avg_execution_time_ms) if row.avg_execution_time_ms else None,
|
||||||
|
success_rate=round(success_rate, 2)
|
||||||
|
))
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to get tool usage stats: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tool-calls/popular", response_model=List[dict])
|
||||||
|
async def get_popular_tools(
|
||||||
|
limit: int = 10,
|
||||||
|
project_id: Optional[int] = None,
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get the most frequently used tools."""
|
||||||
|
try:
|
||||||
|
query = select(
|
||||||
|
ToolCall.tool_name,
|
||||||
|
func.count(ToolCall.id).label("usage_count"),
|
||||||
|
func.max(ToolCall.timestamp).label("last_used")
|
||||||
|
)
|
||||||
|
|
||||||
|
if project_id:
|
||||||
|
query = query.join(Session).where(Session.project_id == project_id)
|
||||||
|
|
||||||
|
query = query.group_by(ToolCall.tool_name).order_by(desc("usage_count")).limit(limit)
|
||||||
|
|
||||||
|
result = await db.execute(query)
|
||||||
|
rows = result.all()
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"tool_name": row.tool_name,
|
||||||
|
"usage_count": row.usage_count,
|
||||||
|
"last_used": row.last_used
|
||||||
|
}
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to get popular tools: {str(e)}"
|
||||||
|
)
|
@ -47,3 +47,76 @@ async def dashboard_conversations(request: Request, db: AsyncSession = Depends(g
|
|||||||
"request": request,
|
"request": request,
|
||||||
"title": "Conversations - Claude Code Tracker"
|
"title": "Conversations - Claude Code Tracker"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_router.get("/dashboard/import", response_class=HTMLResponse)
|
||||||
|
async def dashboard_import(request: Request, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Data import page."""
|
||||||
|
return templates.TemplateResponse("import.html", {
|
||||||
|
"request": request,
|
||||||
|
"title": "Import Data - Claude Code Tracker"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_router.get("/dashboard/projects/{project_id}/timeline", response_class=HTMLResponse)
|
||||||
|
async def dashboard_project_timeline(request: Request, project_id: int, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Project timeline page."""
|
||||||
|
return templates.TemplateResponse("project_timeline.html", {
|
||||||
|
"request": request,
|
||||||
|
"title": f"Timeline - Project {project_id}",
|
||||||
|
"project_id": project_id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_router.get("/dashboard/projects/{project_id}/stats", response_class=HTMLResponse)
|
||||||
|
async def dashboard_project_stats(request: Request, project_id: int, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Project statistics page."""
|
||||||
|
return templates.TemplateResponse("project_stats.html", {
|
||||||
|
"request": request,
|
||||||
|
"title": f"Statistics - Project {project_id}",
|
||||||
|
"project_id": project_id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_router.get("/dashboard/docs", response_class=HTMLResponse)
|
||||||
|
async def dashboard_docs(request: Request, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Documentation overview page."""
|
||||||
|
return templates.TemplateResponse("docs/index.html", {
|
||||||
|
"request": request,
|
||||||
|
"title": "Documentation - Claude Code Tracker"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@dashboard_router.get("/dashboard/docs/{section}", response_class=HTMLResponse)
|
||||||
|
async def dashboard_docs_section(request: Request, section: str, db: AsyncSession = Depends(get_db)):
|
||||||
|
"""Documentation section pages."""
|
||||||
|
# Map section names to templates and titles
|
||||||
|
sections = {
|
||||||
|
"getting-started": {
|
||||||
|
"template": "docs/getting-started.html",
|
||||||
|
"title": "Getting Started - Claude Code Tracker"
|
||||||
|
},
|
||||||
|
"hook-setup": {
|
||||||
|
"template": "docs/hook-setup.html",
|
||||||
|
"title": "Hook Setup Guide - Claude Code Tracker"
|
||||||
|
},
|
||||||
|
"api-reference": {
|
||||||
|
"template": "docs/api-reference.html",
|
||||||
|
"title": "API Reference - Claude Code Tracker"
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"template": "docs/faq.html",
|
||||||
|
"title": "FAQ - Claude Code Tracker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if section not in sections:
|
||||||
|
from fastapi import HTTPException
|
||||||
|
raise HTTPException(status_code=404, detail=f"Documentation section '{section}' not found")
|
||||||
|
|
||||||
|
section_info = sections[section]
|
||||||
|
return templates.TemplateResponse(section_info["template"], {
|
||||||
|
"request": request,
|
||||||
|
"title": section_info["title"],
|
||||||
|
"section": section
|
||||||
|
})
|
@ -92,6 +92,14 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Conversations API
|
// Conversations API
|
||||||
|
async getConversations(projectId = null, limit = 50, offset = 0) {
|
||||||
|
let url = `/conversations?limit=${limit}&offset=${offset}`;
|
||||||
|
if (projectId) {
|
||||||
|
url += `&project_id=${projectId}`;
|
||||||
|
}
|
||||||
|
return this.request(url);
|
||||||
|
}
|
||||||
|
|
||||||
async searchConversations(query, projectId = null, limit = 20) {
|
async searchConversations(query, projectId = null, limit = 20) {
|
||||||
let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`;
|
let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`;
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
|
@ -47,12 +47,24 @@
|
|||||||
Conversations
|
Conversations
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/dashboard/import">
|
||||||
|
<i class="fas fa-download me-1"></i>
|
||||||
|
Import Data
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/dashboard/docs">
|
||||||
|
<i class="fas fa-book me-1"></i>
|
||||||
|
Docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/docs" target="_blank">
|
<a class="nav-link" href="/docs" target="_blank">
|
||||||
<i class="fas fa-book me-1"></i>
|
<i class="fas fa-external-link-alt me-1"></i>
|
||||||
API Docs
|
API Docs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -29,6 +29,10 @@
|
|||||||
<i class="fas fa-search me-1"></i>
|
<i class="fas fa-search me-1"></i>
|
||||||
Search
|
Search
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="clearSearch()">
|
||||||
|
<i class="fas fa-times me-1"></i>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
Search through your conversation history with Claude Code
|
Search through your conversation history with Claude Code
|
||||||
@ -58,9 +62,10 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="conversation-results">
|
<div id="conversation-results">
|
||||||
<div class="text-center text-muted py-5">
|
<div class="text-center text-muted py-5">
|
||||||
<i class="fas fa-search fa-3x mb-3"></i>
|
<div class="spinner-border text-primary" role="status">
|
||||||
<h5>Search Your Conversations</h5>
|
<span class="visually-hidden">Loading conversations...</span>
|
||||||
<p>Enter a search term to find relevant conversations with Claude.</p>
|
</div>
|
||||||
|
<p class="mt-2">Loading your recent conversations...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -73,6 +78,7 @@
|
|||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
loadProjects();
|
loadProjects();
|
||||||
|
loadRecentConversations();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadProjects() {
|
async function loadProjects() {
|
||||||
@ -86,17 +92,91 @@ async function loadProjects() {
|
|||||||
option.textContent = project.name;
|
option.textContent = project.name;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listener for project filtering
|
||||||
|
select.addEventListener('change', function() {
|
||||||
|
const selectedProjectId = this.value ? parseInt(this.value) : null;
|
||||||
|
loadConversationsForProject(selectedProjectId);
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load projects:', error);
|
console.error('Failed to load projects:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadRecentConversations() {
|
||||||
|
await loadConversationsForProject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadConversationsForProject(projectId = null) {
|
||||||
|
const resultsContainer = document.getElementById('conversation-results');
|
||||||
|
const resultsTitle = document.getElementById('results-title');
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
resultsContainer.innerHTML = `
|
||||||
|
<div class="text-center text-muted py-3">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">Loading conversations...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update title based on filtering
|
||||||
|
if (projectId) {
|
||||||
|
const projects = await apiClient.getProjects();
|
||||||
|
const project = projects.find(p => p.id === projectId);
|
||||||
|
resultsTitle.textContent = `Conversations - ${project ? project.name : 'Selected Project'}`;
|
||||||
|
} else {
|
||||||
|
resultsTitle.textContent = 'Recent Conversations';
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversations = await apiClient.getConversations(projectId, 50);
|
||||||
|
|
||||||
|
if (!conversations.length) {
|
||||||
|
const noResultsMessage = projectId ?
|
||||||
|
`No conversations found for this project.` :
|
||||||
|
`No conversation history has been recorded yet.`;
|
||||||
|
|
||||||
|
resultsContainer.innerHTML = `
|
||||||
|
<div class="text-center text-muted py-5">
|
||||||
|
<i class="fas fa-comments fa-3x mb-3"></i>
|
||||||
|
<h5>No Conversations Found</h5>
|
||||||
|
<p>${noResultsMessage}</p>
|
||||||
|
${!projectId ? '<p class="small text-muted">Conversations will appear here as you use Claude Code.</p>' : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySearchResults(conversations, null);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load conversations:', error);
|
||||||
|
resultsContainer.innerHTML = `
|
||||||
|
<div class="text-center text-danger py-4">
|
||||||
|
<i class="fas fa-exclamation-triangle fa-2x mb-2"></i>
|
||||||
|
<p>Failed to load conversations. Please try again.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSearchKeypress(event) {
|
function handleSearchKeypress(event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
searchConversations();
|
searchConversations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
// Clear the search input
|
||||||
|
document.getElementById('search-query').value = '';
|
||||||
|
|
||||||
|
// Return to filtered/unfiltered conversation list based on current project filter
|
||||||
|
const projectId = document.getElementById('project-filter').value || null;
|
||||||
|
loadConversationsForProject(projectId ? parseInt(projectId) : null);
|
||||||
|
}
|
||||||
|
|
||||||
async function searchConversations() {
|
async function searchConversations() {
|
||||||
const query = document.getElementById('search-query').value.trim();
|
const query = document.getElementById('search-query').value.trim();
|
||||||
const projectId = document.getElementById('project-filter').value || null;
|
const projectId = document.getElementById('project-filter').value || null;
|
||||||
@ -164,9 +244,11 @@ function displaySearchResults(results, query) {
|
|||||||
<i class="fas fa-clock me-1"></i>
|
<i class="fas fa-clock me-1"></i>
|
||||||
${ApiUtils.formatRelativeTime(result.timestamp)}
|
${ApiUtils.formatRelativeTime(result.timestamp)}
|
||||||
</small>
|
</small>
|
||||||
<span class="badge bg-success ms-2">
|
${query ? `
|
||||||
${(result.relevance_score * 100).toFixed(0)}% match
|
<span class="badge bg-success ms-2">
|
||||||
</span>
|
${(result.relevance_score * 100).toFixed(0)}% match
|
||||||
|
</span>
|
||||||
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${result.user_prompt ? `
|
${result.user_prompt ? `
|
||||||
@ -175,7 +257,7 @@ function displaySearchResults(results, query) {
|
|||||||
<i class="fas fa-user me-1"></i>
|
<i class="fas fa-user me-1"></i>
|
||||||
You:
|
You:
|
||||||
</strong>
|
</strong>
|
||||||
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query)}</p>
|
<p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query) : ApiUtils.truncateText(result.user_prompt, 200)}</p>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
@ -185,11 +267,11 @@ function displaySearchResults(results, query) {
|
|||||||
<i class="fas fa-robot me-1"></i>
|
<i class="fas fa-robot me-1"></i>
|
||||||
Claude:
|
Claude:
|
||||||
</strong>
|
</strong>
|
||||||
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query)}</p>
|
<p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query) : ApiUtils.truncateText(result.claude_response, 200)}</p>
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
${result.context && result.context.length ? `
|
${result.context && result.context.length && query ? `
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<small class="text-muted">Context snippets:</small>
|
<small class="text-muted">Context snippets:</small>
|
||||||
${result.context.map(snippet => `
|
${result.context.map(snippet => `
|
||||||
|
540
app/dashboard/templates/docs/api-reference.html
Normal file
540
app/dashboard/templates/docs/api-reference.html
Normal file
@ -0,0 +1,540 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">API Reference</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2><i class="fas fa-code me-3"></i>API Reference</h2>
|
||||||
|
<p class="lead text-muted">Complete reference for the Claude Code Tracker REST API</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="/docs" target="_blank" class="btn btn-primary">
|
||||||
|
<i class="fas fa-external-link-alt me-1"></i>
|
||||||
|
Interactive Docs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Base URL -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-server me-2 text-primary"></i>Base URL</h3>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<code>http://localhost:8000</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted mt-2 mb-0">All API endpoints are relative to this base URL. The server runs on port 8000 by default.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Authentication -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-key me-2 text-warning"></i>Authentication</h3>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Currently, the API does not require authentication. All endpoints are publicly accessible when the server is running.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Projects API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-folder-open me-2"></i>Projects API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Get Projects -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/projects</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Retrieve all projects with their statistics.</p>
|
||||||
|
|
||||||
|
<h6>Response Example:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "claude-tracker",
|
||||||
|
"path": "/home/user/claude-tracker",
|
||||||
|
"total_sessions": 5,
|
||||||
|
"total_time_minutes": 240,
|
||||||
|
"files_modified_count": 15,
|
||||||
|
"lines_changed_count": 500,
|
||||||
|
"last_session": "2024-01-15T10:30:00",
|
||||||
|
"created_at": "2024-01-10T09:00:00"
|
||||||
|
}
|
||||||
|
]</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Project Details -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/projects/{project_id}</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Get detailed information about a specific project.</p>
|
||||||
|
|
||||||
|
<h6>Parameters:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><code>project_id</code> (integer, required): Project ID</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Statistics -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/projects/{project_id}/stats</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Get comprehensive statistics for a project including time series data.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sessions API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-play-circle me-2"></i>Sessions API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Start Session -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/sessions/start</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Start a new coding session.</p>
|
||||||
|
|
||||||
|
<h6>Request Body:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded mb-3">
|
||||||
|
<pre><code>{
|
||||||
|
"project_path": "/path/to/project",
|
||||||
|
"start_time": "2024-01-15T10:30:00",
|
||||||
|
"user": "username"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6>Response:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"session_id": "12345",
|
||||||
|
"project_id": 1,
|
||||||
|
"message": "Session started successfully"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- End Session -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/sessions/end</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">End an active coding session.</p>
|
||||||
|
|
||||||
|
<h6>Request Body:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"session_id": "12345",
|
||||||
|
"end_time": "2024-01-15T12:30:00"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Sessions -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/sessions</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Retrieve sessions with optional filtering.</p>
|
||||||
|
|
||||||
|
<h6>Query Parameters:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><code>project_id</code> (integer, optional): Filter by project</li>
|
||||||
|
<li><code>start_date</code> (date, optional): Sessions after this date</li>
|
||||||
|
<li><code>end_date</code> (date, optional): Sessions before this date</li>
|
||||||
|
<li><code>limit</code> (integer, optional): Maximum number of results</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Conversations API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-comments me-2"></i>Conversations API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Create Conversation -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/conversations</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Record a new conversation message.</p>
|
||||||
|
|
||||||
|
<h6>Request Body:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"session_id": "12345",
|
||||||
|
"content": "User message or assistant response",
|
||||||
|
"role": "user|assistant",
|
||||||
|
"timestamp": "2024-01-15T10:35:00"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Conversations -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/conversations/search</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Search conversations by content, project, or date.</p>
|
||||||
|
|
||||||
|
<h6>Query Parameters:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><code>q</code> (string): Search query for content</li>
|
||||||
|
<li><code>project_id</code> (integer): Filter by project</li>
|
||||||
|
<li><code>start_date</code> (date): Messages after this date</li>
|
||||||
|
<li><code>end_date</code> (date): Messages before this date</li>
|
||||||
|
<li><code>role</code> (string): Filter by role (user/assistant)</li>
|
||||||
|
<li><code>limit</code> (integer): Maximum results (default: 50)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Conversations -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/conversations</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Retrieve conversations with pagination.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Activities API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-file-edit me-2"></i>Activities API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Record Activity -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/activities</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Record a file modification activity.</p>
|
||||||
|
|
||||||
|
<h6>Request Body:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"session_id": "12345",
|
||||||
|
"file_path": "/path/to/modified/file.py",
|
||||||
|
"action": "created|modified|deleted",
|
||||||
|
"lines_added": 10,
|
||||||
|
"lines_removed": 5,
|
||||||
|
"timestamp": "2024-01-15T10:40:00"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Activities -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/activities</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Retrieve file modification activities.</p>
|
||||||
|
|
||||||
|
<h6>Query Parameters:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><code>session_id</code> (string): Filter by session</li>
|
||||||
|
<li><code>project_id</code> (integer): Filter by project</li>
|
||||||
|
<li><code>file_path</code> (string): Filter by file path</li>
|
||||||
|
<li><code>action</code> (string): Filter by action type</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tool Calls API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-tools me-2"></i>Tool Calls API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Record Tool Call -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/tool-calls</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Record a tool call made during a Claude Code session.</p>
|
||||||
|
|
||||||
|
<h6>Request Body:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"tool_name": "Read",
|
||||||
|
"parameters": {
|
||||||
|
"file_path": "/path/to/file.py",
|
||||||
|
"limit": 100
|
||||||
|
},
|
||||||
|
"result_status": "success",
|
||||||
|
"execution_time_ms": 250,
|
||||||
|
"timestamp": "2024-01-15T10:45:00"
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Tool Usage Stats -->
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/tool-calls/stats</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Get tool usage statistics with optional filtering.</p>
|
||||||
|
|
||||||
|
<h6>Query Parameters:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><code>project_id</code> (integer, optional): Filter by project</li>
|
||||||
|
<li><code>start_date</code> (date, optional): Tools used after this date</li>
|
||||||
|
<li><code>end_date</code> (date, optional): Tools used before this date</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Get Session Tool Calls -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-success me-2 px-3">GET</span>
|
||||||
|
<code>/api/tool-calls/session/{session_id}</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Get all tool calls for a specific session.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Data Import API -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-upload me-2"></i>Data Import API</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Import Claude Data -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
|
||||||
|
<code>/api/import/claude-data</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">Import data from a Claude.json file.</p>
|
||||||
|
|
||||||
|
<h6>Request:</h6>
|
||||||
|
<p>Multipart form data with file upload:</p>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>Content-Type: multipart/form-data
|
||||||
|
|
||||||
|
file: [claude.json file]</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6>Response:</h6>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"success": true,
|
||||||
|
"projects_imported": 15,
|
||||||
|
"sessions_imported": 42,
|
||||||
|
"conversations_imported": 158,
|
||||||
|
"activities_imported": 89,
|
||||||
|
"errors": []
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Responses -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-exclamation-triangle me-2 text-danger"></i>Error Responses</h3>
|
||||||
|
<p class="text-muted mb-4">The API uses standard HTTP status codes and returns error details in JSON format.</p>
|
||||||
|
|
||||||
|
<h5>Common Status Codes:</h5>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Meaning</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-success">200</span></td>
|
||||||
|
<td>OK</td>
|
||||||
|
<td>Request successful</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-info">201</span></td>
|
||||||
|
<td>Created</td>
|
||||||
|
<td>Resource created successfully</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-warning text-dark">400</span></td>
|
||||||
|
<td>Bad Request</td>
|
||||||
|
<td>Invalid request data</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-danger">404</span></td>
|
||||||
|
<td>Not Found</td>
|
||||||
|
<td>Resource not found</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-danger">500</span></td>
|
||||||
|
<td>Server Error</td>
|
||||||
|
<td>Internal server error</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Error Response Format:</h5>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<pre><code>{
|
||||||
|
"error": true,
|
||||||
|
"message": "Detailed error description",
|
||||||
|
"code": "ERROR_CODE",
|
||||||
|
"details": {
|
||||||
|
"field": "Additional error context"
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rate Limiting -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-tachometer-alt me-2 text-info"></i>Rate Limiting</h3>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Currently, no rate limiting is implemented. However, it's recommended to avoid making excessive requests to maintain optimal performance.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="sticky-top" style="top: 2rem;">
|
||||||
|
<!-- Quick Links -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="fas fa-external-link-alt me-2"></i>Quick Links</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/docs" target="_blank" class="btn btn-primary">
|
||||||
|
<i class="fas fa-code me-2"></i>
|
||||||
|
Interactive API Docs
|
||||||
|
</a>
|
||||||
|
<a href="http://localhost:8000/api/projects" target="_blank" class="btn btn-outline-success">
|
||||||
|
<i class="fas fa-folder-open me-2"></i>
|
||||||
|
Test Projects API
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table of Contents -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-list me-2"></i>API Endpoints</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<nav class="nav nav-pills flex-column">
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#projects">Projects API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#sessions">Sessions API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#conversations">Conversations API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#activities">Activities API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#tool-calls">Tool Calls API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#import">Data Import API</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#errors">Error Responses</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Code Examples -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-code me-2"></i>Code Examples</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6>JavaScript (Fetch)</h6>
|
||||||
|
<div class="bg-dark text-light p-2 rounded mb-3">
|
||||||
|
<code class="small">
|
||||||
|
fetch('/api/projects')<br>
|
||||||
|
.then(r => r.json())<br>
|
||||||
|
.then(data => console.log(data))
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6>Python (Requests)</h6>
|
||||||
|
<div class="bg-dark text-light p-2 rounded mb-3">
|
||||||
|
<code class="small">
|
||||||
|
import requests<br>
|
||||||
|
r = requests.get('http://localhost:8000/api/projects')<br>
|
||||||
|
print(r.json())
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h6>cURL</h6>
|
||||||
|
<div class="bg-dark text-light p-2 rounded">
|
||||||
|
<code class="small">
|
||||||
|
curl -X GET http://localhost:8000/api/projects
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SDK Information -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-puzzle-piece me-2"></i>SDK & Libraries</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-muted mb-3">Currently, there are no official SDKs. Use standard HTTP libraries for your preferred language.</p>
|
||||||
|
<h6>Recommended Libraries:</h6>
|
||||||
|
<ul class="list-unstyled small">
|
||||||
|
<li class="mb-1"><strong>JavaScript:</strong> fetch, axios</li>
|
||||||
|
<li class="mb-1"><strong>Python:</strong> requests, httpx</li>
|
||||||
|
<li class="mb-1"><strong>Go:</strong> net/http</li>
|
||||||
|
<li class="mb-1"><strong>Java:</strong> OkHttp, HttpClient</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
784
app/dashboard/templates/docs/faq.html
Normal file
784
app/dashboard/templates/docs/faq.html
Normal file
@ -0,0 +1,784 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">FAQ</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2><i class="fas fa-question-circle me-3"></i>Frequently Asked Questions</h2>
|
||||||
|
<p class="lead text-muted">Common questions and troubleshooting guide</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Search FAQ -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" class="form-control" placeholder="Search FAQ..." id="faqSearch">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- General Questions -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-info-circle me-2"></i>General Questions</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="generalAccordion">
|
||||||
|
<!-- Q1 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="what is claude code tracker purpose features">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general1">
|
||||||
|
<i class="fas fa-question me-2 text-primary"></i>
|
||||||
|
What is Claude Code Tracker and what does it do?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="general1" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>Claude Code Tracker is a development intelligence system that helps you understand and analyze your coding activity when using Claude Code. It provides:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Session Tracking:</strong> Monitor your development sessions and time spent</li>
|
||||||
|
<li><strong>Conversation History:</strong> Search and browse through all your Claude conversations</li>
|
||||||
|
<li><strong>Project Analytics:</strong> Get insights into file modifications, lines changed, and productivity patterns</li>
|
||||||
|
<li><strong>Timeline Visualization:</strong> See your development progress over time</li>
|
||||||
|
<li><strong>Data Privacy:</strong> All data stays local on your machine</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q2 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="data privacy local cloud storage security">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general2">
|
||||||
|
<i class="fas fa-shield-alt me-2 text-success"></i>
|
||||||
|
Is my data secure? Where is it stored?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="general2" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
<strong>Yes, your data is completely secure!</strong>
|
||||||
|
</div>
|
||||||
|
<p>Claude Code Tracker prioritizes your privacy:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Local Storage:</strong> All data is stored locally in an SQLite database on your machine</li>
|
||||||
|
<li><strong>No Cloud Services:</strong> No data is sent to external servers or cloud services</li>
|
||||||
|
<li><strong>No Network Dependencies:</strong> Works completely offline after initial setup</li>
|
||||||
|
<li><strong>Your Control:</strong> You have full control over your data and can delete it anytime</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mb-0"><strong>Database Location:</strong> <code>./data/tracker.db</code> in your installation directory</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q3 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="requirements system python version dependencies">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general3">
|
||||||
|
<i class="fas fa-server me-2 text-info"></i>
|
||||||
|
What are the system requirements?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="general3" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Minimum Requirements:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Python 3.8 or higher</li>
|
||||||
|
<li>2GB RAM</li>
|
||||||
|
<li>100MB free disk space</li>
|
||||||
|
<li>Modern web browser</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Supported Platforms:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Windows 10/11</li>
|
||||||
|
<li>macOS 10.15+</li>
|
||||||
|
<li>Linux (Ubuntu, CentOS, etc.)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-info mt-3">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Claude Code CLI must be installed and configured separately.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Setup & Installation -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-download me-2"></i>Setup & Installation</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="setupAccordion">
|
||||||
|
<!-- Q4 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="installation setup install getting started first time">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup1">
|
||||||
|
<i class="fas fa-play-circle me-2 text-success"></i>
|
||||||
|
How do I install and set up Claude Code Tracker?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="setup1" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ol>
|
||||||
|
<li class="mb-2"><strong>Download:</strong> Clone or download the repository</li>
|
||||||
|
<li class="mb-2"><strong>Install Dependencies:</strong> Run <code>pip install -r requirements.txt</code></li>
|
||||||
|
<li class="mb-2"><strong>Start Server:</strong> Run <code>python main.py</code></li>
|
||||||
|
<li class="mb-2"><strong>Access Dashboard:</strong> Open <code>http://localhost:8000/dashboard</code></li>
|
||||||
|
<li class="mb-2"><strong>Import Data:</strong> Use the <a href="/dashboard/import">Import Data</a> page to load your existing Claude data</li>
|
||||||
|
</ol>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
See the <a href="/dashboard/docs/getting-started">Getting Started Guide</a> for detailed instructions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q5 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="claude json file location find import data">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup2">
|
||||||
|
<i class="fas fa-file-code me-2 text-warning"></i>
|
||||||
|
Where can I find my claude.json file?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="setup2" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>The claude.json file contains your Claude Code conversation history. Location varies by operating system:</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3 text-center">
|
||||||
|
<i class="fab fa-apple fa-2x text-secondary mb-2"></i>
|
||||||
|
<h6>macOS</h6>
|
||||||
|
<code class="small">~/.claude.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3 text-center">
|
||||||
|
<i class="fab fa-linux fa-2x text-warning mb-2"></i>
|
||||||
|
<h6>Linux</h6>
|
||||||
|
<code class="small">~/.claude.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3 text-center">
|
||||||
|
<i class="fab fa-windows fa-2x text-primary mb-2"></i>
|
||||||
|
<h6>Windows</h6>
|
||||||
|
<code class="small">%USERPROFILE%\.claude.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<h6 class="text-light">Quick commands to locate the file:</h6>
|
||||||
|
<p class="mb-2"><strong>macOS/Linux:</strong> <code>ls -la ~/.claude.json</code></p>
|
||||||
|
<p class="mb-0"><strong>Windows:</strong> <code>dir %USERPROFILE%\.claude.json</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q6 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="hooks setup configuration not working automatic tracking">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup3">
|
||||||
|
<i class="fas fa-link me-2 text-info"></i>
|
||||||
|
How do I set up hooks for automatic tracking?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="setup3" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>Hooks enable real-time tracking of your Claude Code sessions. Here's the quick setup:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li class="mb-2">Locate your Claude Code settings file:
|
||||||
|
<ul>
|
||||||
|
<li>macOS/Linux: <code>~/.config/claude/settings.json</code></li>
|
||||||
|
<li>Windows: <code>%APPDATA%\claude\settings.json</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">Add hook configuration to the settings file</li>
|
||||||
|
<li class="mb-2">Ensure Claude Code Tracker server is running</li>
|
||||||
|
<li>Test by starting a new Claude Code session</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-book me-2"></i>
|
||||||
|
<strong>Detailed Instructions:</strong> See the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a> for complete configuration examples.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Troubleshooting -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-tools me-2"></i>Troubleshooting</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="troubleshootingAccordion">
|
||||||
|
<!-- Q7 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="import fails error upload data corrupted file">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble1">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>
|
||||||
|
Data import is failing. What should I do?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="trouble1" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Common causes and solutions:</strong></p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6><i class="fas fa-file-alt me-2 text-warning"></i>File Issues:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Verify the JSON file is valid</li>
|
||||||
|
<li>Check file permissions (readable)</li>
|
||||||
|
<li>Ensure file is not corrupted</li>
|
||||||
|
<li>Try with a smaller JSON file first</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6><i class="fas fa-server me-2 text-info"></i>System Issues:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Check available disk space (need ~100MB)</li>
|
||||||
|
<li>Verify database permissions</li>
|
||||||
|
<li>Restart the server and try again</li>
|
||||||
|
<li>Check server logs for detailed errors</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<h6 class="text-light">Validate your JSON file:</h6>
|
||||||
|
<code>python -m json.tool ~/.claude.json > /dev/null && echo "Valid JSON" || echo "Invalid JSON"</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q8 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="statistics zero empty projects showing wrong data recalculate">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble2">
|
||||||
|
<i class="fas fa-chart-line me-2 text-primary"></i>
|
||||||
|
Project statistics are showing zeros. How to fix?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="trouble2" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>This commonly happens after importing data. The statistics need to be recalculated.</p>
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-wrench me-2"></i>
|
||||||
|
<strong>Quick Fix:</strong> Run the recalculation script
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mb-3">
|
||||||
|
<h6 class="text-light">Run this command in your installation directory:</h6>
|
||||||
|
<code>python recalculate_project_stats.py</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>This script will:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Recalculate session counts and duration</li>
|
||||||
|
<li>Count file modifications and line changes</li>
|
||||||
|
<li>Update last session times</li>
|
||||||
|
<li>Fix all project statistics</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="mb-0"><strong>Note:</strong> The process may take a few minutes for large datasets.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q9 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="hooks not working automatic tracking session start end">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble3">
|
||||||
|
<i class="fas fa-unlink me-2 text-warning"></i>
|
||||||
|
Hooks are not working. Sessions aren't being tracked automatically.
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="trouble3" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Checklist to troubleshoot hooks:</strong></p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Server Status:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>✅ Tracker server is running</li>
|
||||||
|
<li>✅ Server accessible at localhost:8000</li>
|
||||||
|
<li>✅ No firewall blocking connections</li>
|
||||||
|
<li>✅ Check server logs for errors</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Configuration:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>✅ Settings.json file exists</li>
|
||||||
|
<li>✅ Hook URLs are correct</li>
|
||||||
|
<li>✅ JSON syntax is valid</li>
|
||||||
|
<li>✅ curl is installed</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<h6 class="text-light">Test hook manually:</h6>
|
||||||
|
<code>curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{"project_path": "/test", "start_time": "2024-01-01T12:00:00"}'</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mt-3">
|
||||||
|
<i class="fas fa-book me-2"></i>
|
||||||
|
See the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a> for detailed troubleshooting steps.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q10 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="server won't start port error permission database">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble4">
|
||||||
|
<i class="fas fa-server me-2 text-danger"></i>
|
||||||
|
The server won't start. What's wrong?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="trouble4" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Common issues and solutions:</strong></p>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Error</th>
|
||||||
|
<th>Cause</th>
|
||||||
|
<th>Solution</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Port 8000 in use</td>
|
||||||
|
<td>Another service using port</td>
|
||||||
|
<td>Kill process or use different port</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Permission denied</td>
|
||||||
|
<td>Insufficient file permissions</td>
|
||||||
|
<td>Check directory permissions</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Module not found</td>
|
||||||
|
<td>Missing dependencies</td>
|
||||||
|
<td>Run <code>pip install -r requirements.txt</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Database error</td>
|
||||||
|
<td>Corrupted database</td>
|
||||||
|
<td>Delete <code>data/tracker.db</code> and restart</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<h6 class="text-light">Check what's using port 8000:</h6>
|
||||||
|
<p class="mb-2"><strong>macOS/Linux:</strong> <code>lsof -i :8000</code></p>
|
||||||
|
<p class="mb-0"><strong>Windows:</strong> <code>netstat -ano | findstr :8000</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Usage Questions -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-users me-2"></i>Usage Questions</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="usageAccordion">
|
||||||
|
<!-- Q11 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="search conversations find messages history filter">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage1">
|
||||||
|
<i class="fas fa-search me-2 text-primary"></i>
|
||||||
|
How do I search through my conversations?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="usage1" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p>The <a href="/dashboard/conversations">Conversations</a> page provides powerful search capabilities:</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Search Options:</h6>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Text Search:</strong> Search message content</li>
|
||||||
|
<li><strong>Project Filter:</strong> Filter by specific project</li>
|
||||||
|
<li><strong>Date Range:</strong> Filter by time period</li>
|
||||||
|
<li><strong>Role Filter:</strong> User vs Assistant messages</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Search Tips:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Use quotes for exact phrases</li>
|
||||||
|
<li>Search is case-insensitive</li>
|
||||||
|
<li>Combine filters for precise results</li>
|
||||||
|
<li>Use wildcards (*) for partial matches</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-lightbulb me-2"></i>
|
||||||
|
<strong>Pro Tip:</strong> Use the project filter to focus on conversations from specific coding projects.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q12 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="export data backup conversations sessions save">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage2">
|
||||||
|
<i class="fas fa-download me-2 text-success"></i>
|
||||||
|
Can I export my data or create backups?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="usage2" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Yes! You have full control over your data:</strong></p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Database Backup:</h6>
|
||||||
|
<p>The complete database is stored in:</p>
|
||||||
|
<code class="d-block p-2 bg-light rounded">./data/tracker.db</code>
|
||||||
|
<p class="mt-2 small text-muted">Simply copy this file to create a complete backup.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>API Export:</h6>
|
||||||
|
<p>Use the API to export specific data:</p>
|
||||||
|
<ul class="small">
|
||||||
|
<li>GET /api/projects (all projects)</li>
|
||||||
|
<li>GET /api/sessions (all sessions)</li>
|
||||||
|
<li>GET /api/conversations (all conversations)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-shield-alt me-2"></i>
|
||||||
|
<strong>Recommendation:</strong> Regularly backup the <code>data/</code> directory to preserve your development history.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q13 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="delete remove data project session conversation clear">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage3">
|
||||||
|
<i class="fas fa-trash me-2 text-danger"></i>
|
||||||
|
How do I delete specific projects or conversations?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="usage3" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
<strong>Note:</strong> Currently, there's no built-in UI for deleting individual items. This feature is planned for future releases.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><strong>Workarounds:</strong></p>
|
||||||
|
<ol>
|
||||||
|
<li class="mb-2"><strong>Database Access:</strong> Use an SQLite client to directly modify the database</li>
|
||||||
|
<li class="mb-2"><strong>Fresh Start:</strong> Delete the entire database file and re-import</li>
|
||||||
|
<li class="mb-2"><strong>API (Advanced):</strong> Use custom scripts with the API endpoints</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<h6 class="text-light">Nuclear option - Clear all data:</h6>
|
||||||
|
<code>rm data/tracker.db && python main.py</code>
|
||||||
|
<br><small class="text-muted">This will create a fresh database on next startup</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Performance & Optimization -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-tachometer-alt me-2"></i>Performance & Optimization</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="accordion" id="performanceAccordion">
|
||||||
|
<!-- Q14 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="slow performance large datasets optimize speed">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf1">
|
||||||
|
<i class="fas fa-clock me-2 text-warning"></i>
|
||||||
|
The dashboard is slow with large amounts of data. How to optimize?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="perf1" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Optimization strategies for large datasets:</strong></p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Quick Fixes:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Use date filters to limit results</li>
|
||||||
|
<li>Filter by specific projects</li>
|
||||||
|
<li>Close unused browser tabs</li>
|
||||||
|
<li>Restart the server periodically</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Advanced Options:</h6>
|
||||||
|
<ul>
|
||||||
|
<li>Increase server memory allocation</li>
|
||||||
|
<li>Use pagination for large results</li>
|
||||||
|
<li>Consider archiving old data</li>
|
||||||
|
<li>Run server on more powerful hardware</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
<strong>Typical Performance:</strong> The system handles 10,000+ conversations efficiently. Performance degrades with 50,000+ entries.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Q15 -->
|
||||||
|
<div class="accordion-item border-0 faq-item" data-keywords="disk space database size storage cleanup">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf2">
|
||||||
|
<i class="fas fa-hdd me-2 text-info"></i>
|
||||||
|
How much disk space does the tracker use?
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="perf2" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Typical storage requirements:</strong></p>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Data Size</th>
|
||||||
|
<th>Conversations</th>
|
||||||
|
<th>Disk Usage</th>
|
||||||
|
<th>Performance</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Small</td>
|
||||||
|
<td>< 1,000</td>
|
||||||
|
<td>< 10MB</td>
|
||||||
|
<td>Excellent</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Medium</td>
|
||||||
|
<td>1,000 - 10,000</td>
|
||||||
|
<td>10-100MB</td>
|
||||||
|
<td>Good</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Large</td>
|
||||||
|
<td>10,000 - 50,000</td>
|
||||||
|
<td>100MB-1GB</td>
|
||||||
|
<td>Fair</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Very Large</td>
|
||||||
|
<td>> 50,000</td>
|
||||||
|
<td>> 1GB</td>
|
||||||
|
<td>Consider archiving</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<h6 class="text-light">Check current database size:</h6>
|
||||||
|
<p class="mb-2"><strong>Unix:</strong> <code>du -h data/tracker.db</code></p>
|
||||||
|
<p class="mb-0"><strong>Windows:</strong> <code>dir data\tracker.db</code></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="sticky-top" style="top: 2rem;">
|
||||||
|
<!-- Quick Help -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0"><i class="fas fa-life-ring me-2"></i>Quick Help</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/dashboard/docs/getting-started" class="btn btn-outline-primary btn-sm">
|
||||||
|
<i class="fas fa-rocket me-1"></i>
|
||||||
|
Getting Started
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/docs/hook-setup" class="btn btn-outline-success btn-sm">
|
||||||
|
<i class="fas fa-link me-1"></i>
|
||||||
|
Hook Setup Guide
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/docs/api-reference" class="btn btn-outline-info btn-sm">
|
||||||
|
<i class="fas fa-code me-1"></i>
|
||||||
|
API Reference
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Categories -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-tags me-2"></i>Categories</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<span class="badge bg-primary" data-filter="general">General</span>
|
||||||
|
<span class="badge bg-success" data-filter="setup">Setup</span>
|
||||||
|
<span class="badge bg-warning" data-filter="troubleshooting">Troubleshooting</span>
|
||||||
|
<span class="badge bg-info" data-filter="usage">Usage</span>
|
||||||
|
<span class="badge bg-secondary" data-filter="performance">Performance</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Common Issues -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-exclamation-triangle me-2"></i>Top Issues</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-circle text-danger me-2" style="font-size: 0.5rem;"></i>
|
||||||
|
<a href="#trouble2" class="text-decoration-none small">Statistics showing zeros</a>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-circle text-warning me-2" style="font-size: 0.5rem;"></i>
|
||||||
|
<a href="#trouble1" class="text-decoration-none small">Import failing</a>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-circle text-info me-2" style="font-size: 0.5rem;"></i>
|
||||||
|
<a href="#trouble3" class="text-decoration-none small">Hooks not working</a>
|
||||||
|
</li>
|
||||||
|
<li class="mb-0">
|
||||||
|
<i class="fas fa-circle text-secondary me-2" style="font-size: 0.5rem;"></i>
|
||||||
|
<a href="#trouble4" class="text-decoration-none small">Server won't start</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// FAQ Search functionality
|
||||||
|
document.getElementById('faqSearch').addEventListener('input', function(e) {
|
||||||
|
const searchTerm = e.target.value.toLowerCase();
|
||||||
|
const faqItems = document.querySelectorAll('.faq-item');
|
||||||
|
|
||||||
|
faqItems.forEach(item => {
|
||||||
|
const keywords = item.dataset.keywords.toLowerCase();
|
||||||
|
const text = item.textContent.toLowerCase();
|
||||||
|
|
||||||
|
if (keywords.includes(searchTerm) || text.includes(searchTerm) || searchTerm === '') {
|
||||||
|
item.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
item.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Category filtering
|
||||||
|
document.querySelectorAll('[data-filter]').forEach(badge => {
|
||||||
|
badge.addEventListener('click', function() {
|
||||||
|
const filter = this.dataset.filter.toLowerCase();
|
||||||
|
const faqItems = document.querySelectorAll('.faq-item');
|
||||||
|
|
||||||
|
// Toggle active state
|
||||||
|
document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
|
||||||
|
faqItems.forEach(item => {
|
||||||
|
const keywords = item.dataset.keywords.toLowerCase();
|
||||||
|
|
||||||
|
if (keywords.includes(filter) || filter === '') {
|
||||||
|
item.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
item.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
349
app/dashboard/templates/docs/getting-started.html
Normal file
349
app/dashboard/templates/docs/getting-started.html
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Getting Started</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2><i class="fas fa-rocket me-3"></i>Getting Started</h2>
|
||||||
|
<p class="lead text-muted">Learn the basics and get Claude Code Tracker up and running</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted">
|
||||||
|
<i class="fas fa-clock me-1"></i>
|
||||||
|
5 min read
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Overview -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-info-circle me-2 text-primary"></i>What is Claude Code Tracker?</h3>
|
||||||
|
<p>Claude Code Tracker is a development intelligence system that helps you understand and analyze your coding activity when using Claude Code. It automatically tracks your sessions, conversations, file modifications, and provides insights into your development patterns.</p>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5><i class="fas fa-check-circle me-2 text-success"></i>Key Benefits</h5>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2"><i class="fas fa-chart-line text-primary me-2"></i>Track development productivity</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-history text-info me-2"></i>Browse conversation history</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-folder text-warning me-2"></i>Analyze project patterns</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-shield-alt text-success me-2"></i>Keep data local and private</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5><i class="fas fa-cogs me-2 text-info"></i>Core Features</h5>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2"><i class="fas fa-upload text-secondary me-2"></i>Import existing Claude data</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-link text-success me-2"></i>Real-time hook integration</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-search text-primary me-2"></i>Advanced search and filtering</li>
|
||||||
|
<li class="mb-2"><i class="fas fa-chart-bar text-warning me-2"></i>Visual analytics and insights</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Start Steps -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-play-circle me-2"></i>Quick Start Guide</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Step 1: Import Data -->
|
||||||
|
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
|
||||||
|
<div class="bg-primary rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>1</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Import Your Existing Data</h5>
|
||||||
|
<p class="text-muted mb-3">Start by importing your existing Claude Code conversation history from your <code>~/.claude.json</code> file.</p>
|
||||||
|
|
||||||
|
<div class="bg-light p-3 rounded mb-3">
|
||||||
|
<h6><i class="fas fa-terminal me-2"></i>Steps:</h6>
|
||||||
|
<ol class="mb-0">
|
||||||
|
<li>Navigate to the <a href="/dashboard/import">Import Data</a> page</li>
|
||||||
|
<li>Click "Choose File" and select your <code>~/.claude.json</code> file</li>
|
||||||
|
<li>Click "Import Data" and wait for processing to complete</li>
|
||||||
|
<li>Review the import summary for any issues</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
<strong>Location:</strong> Your Claude data file is typically located at <code>~/.claude.json</code> on macOS/Linux or <code>%USERPROFILE%\.claude.json</code> on Windows.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Set Up Hooks -->
|
||||||
|
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
|
||||||
|
<div class="bg-success rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>2</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Configure Claude Code Hooks</h5>
|
||||||
|
<p class="text-muted mb-3">Set up hooks to automatically track new sessions and conversations in real-time.</p>
|
||||||
|
|
||||||
|
<div class="bg-light p-3 rounded mb-3">
|
||||||
|
<h6><i class="fas fa-code me-2"></i>Hook Configuration:</h6>
|
||||||
|
<p class="mb-2">Add this configuration to your Claude Code settings:</p>
|
||||||
|
<pre class="bg-dark text-light p-3 rounded"><code>{
|
||||||
|
"hooks": {
|
||||||
|
"session_start": "curl -X POST http://localhost:8000/api/sessions/start",
|
||||||
|
"session_end": "curl -X POST http://localhost:8000/api/sessions/end",
|
||||||
|
"conversation_update": "curl -X POST http://localhost:8000/api/conversations"
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
<strong>Note:</strong> Detailed hook setup instructions are available in the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3: Explore Dashboard -->
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-info rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>3</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Explore Your Data</h5>
|
||||||
|
<p class="text-muted mb-3">Navigate through the dashboard to discover insights about your development activity.</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<h6><a href="/dashboard/projects" class="text-decoration-none"><i class="fas fa-folder-open me-2"></i>Projects</a></h6>
|
||||||
|
<small class="text-muted">View project statistics, timelines, and detailed analytics</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<h6><a href="/dashboard/conversations" class="text-decoration-none"><i class="fas fa-comments me-2"></i>Conversations</a></h6>
|
||||||
|
<small class="text-muted">Search and browse through all your Claude conversations</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- System Requirements -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-server me-2 text-info"></i>System Requirements</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5>Software Requirements</h5>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fab fa-python text-primary me-2"></i>
|
||||||
|
<strong>Python 3.8+</strong>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-terminal text-secondary me-2"></i>
|
||||||
|
<strong>Claude Code CLI</strong>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-globe text-info me-2"></i>
|
||||||
|
<strong>Web browser</strong> (modern browser recommended)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h5>Hardware Recommendations</h5>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-memory text-warning me-2"></i>
|
||||||
|
<strong>2GB+ RAM</strong>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-hdd text-success me-2"></i>
|
||||||
|
<strong>100MB+ disk space</strong>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-network-wired text-primary me-2"></i>
|
||||||
|
<strong>Network access</strong> for web interface
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Troubleshooting -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-tools me-2 text-warning"></i>Common Issues</h3>
|
||||||
|
|
||||||
|
<div class="accordion" id="troubleshootingAccordion">
|
||||||
|
<!-- Issue 1 -->
|
||||||
|
<div class="accordion-item border-0">
|
||||||
|
<h2 class="accordion-header" id="issue1">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1">
|
||||||
|
<i class="fas fa-file-import me-2 text-danger"></i>
|
||||||
|
Import fails or shows errors
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Invalid or corrupted <code>.claude.json</code> file</li>
|
||||||
|
<li>Insufficient disk space</li>
|
||||||
|
<li>File permissions issues</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Solutions:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Verify the JSON file is valid using a JSON validator</li>
|
||||||
|
<li>Check available disk space (need ~100MB minimum)</li>
|
||||||
|
<li>Ensure the application has read/write permissions</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Issue 2 -->
|
||||||
|
<div class="accordion-item border-0">
|
||||||
|
<h2 class="accordion-header" id="issue2">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2">
|
||||||
|
<i class="fas fa-link me-2 text-warning"></i>
|
||||||
|
Hooks not working
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>Check these items:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Claude Code Tracker server is running on port 8000</li>
|
||||||
|
<li>Hook URLs are correctly configured</li>
|
||||||
|
<li>Network connectivity between Claude Code and the tracker</li>
|
||||||
|
<li>Hook syntax is valid JSON</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Issue 3 -->
|
||||||
|
<div class="accordion-item border-0">
|
||||||
|
<h2 class="accordion-header" id="issue3">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3">
|
||||||
|
<i class="fas fa-chart-line me-2 text-info"></i>
|
||||||
|
Statistics showing zeros
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapse3" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p><strong>This can happen after import. Run the recalculation script:</strong></p>
|
||||||
|
<pre class="bg-dark text-light p-3 rounded"><code>python recalculate_project_stats.py</code></pre>
|
||||||
|
<p class="mt-2">This will recalculate all project statistics from your imported data.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="sticky-top" style="top: 2rem;">
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h5 class="mb-0"><i class="fas fa-rocket me-2"></i>Quick Actions</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/dashboard/import" class="btn btn-success">
|
||||||
|
<i class="fas fa-upload me-2"></i>
|
||||||
|
Import Data Now
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/docs/hook-setup" class="btn btn-outline-primary">
|
||||||
|
<i class="fas fa-link me-2"></i>
|
||||||
|
Setup Hooks
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/projects" class="btn btn-outline-info">
|
||||||
|
<i class="fas fa-folder-open me-2"></i>
|
||||||
|
View Projects
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table of Contents -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-list me-2"></i>Contents</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<nav class="nav nav-pills flex-column">
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#overview">What is Claude Code Tracker?</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#quickstart">Quick Start Guide</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#requirements">System Requirements</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#troubleshooting">Common Issues</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Related Links -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-external-link-alt me-2"></i>Related</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
<li class="mb-2">
|
||||||
|
<a href="/dashboard/docs/hook-setup" class="text-decoration-none">
|
||||||
|
<i class="fas fa-link me-2 text-success"></i>
|
||||||
|
Hook Setup Guide
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<a href="/dashboard/docs/api-reference" class="text-decoration-none">
|
||||||
|
<i class="fas fa-code me-2 text-info"></i>
|
||||||
|
API Reference
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<a href="/dashboard/docs/faq" class="text-decoration-none">
|
||||||
|
<i class="fas fa-question-circle me-2 text-warning"></i>
|
||||||
|
FAQ
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/docs" target="_blank" class="text-decoration-none">
|
||||||
|
<i class="fas fa-external-link-alt me-2 text-primary"></i>
|
||||||
|
API Documentation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
483
app/dashboard/templates/docs/hook-setup.html
Normal file
483
app/dashboard/templates/docs/hook-setup.html
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<div>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Hook Setup Guide</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h2><i class="fas fa-link me-3"></i>Hook Setup Guide</h2>
|
||||||
|
<p class="lead text-muted">Configure Claude Code hooks for automatic activity tracking</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted">
|
||||||
|
<i class="fas fa-clock me-1"></i>
|
||||||
|
10 min read
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<!-- Overview -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-info-circle me-2 text-primary"></i>What are Claude Code Hooks?</h3>
|
||||||
|
<p>Claude Code hooks are callback mechanisms that allow external applications (like this tracker) to receive notifications about various events in your Claude Code sessions. They enable real-time tracking of your development activity without manual intervention.</p>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-lightbulb me-2"></i>
|
||||||
|
<strong>Benefits:</strong> Hooks provide automatic, real-time data collection, ensuring you never miss tracking a session or conversation.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hook Types -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-list me-2"></i>Available Hook Types</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5><i class="fas fa-play-circle me-2 text-success"></i>session_start</h5>
|
||||||
|
<p class="text-muted small mb-2">Triggered when a new Claude Code session begins</p>
|
||||||
|
<div class="badge bg-success">Required</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5><i class="fas fa-stop-circle me-2 text-danger"></i>session_end</h5>
|
||||||
|
<p class="text-muted small mb-2">Triggered when a Claude Code session ends</p>
|
||||||
|
<div class="badge bg-success">Required</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5><i class="fas fa-comments me-2 text-info"></i>conversation_update</h5>
|
||||||
|
<p class="text-muted small mb-2">Triggered when conversation messages are added</p>
|
||||||
|
<div class="badge bg-warning">Recommended</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5><i class="fas fa-file-edit me-2 text-warning"></i>file_modified</h5>
|
||||||
|
<p class="text-muted small mb-2">Triggered when files are created or modified</p>
|
||||||
|
<div class="badge bg-info">Optional</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5><i class="fas fa-tools me-2 text-primary"></i>tool_call</h5>
|
||||||
|
<p class="text-muted small mb-2">Triggered when Claude Code tools are used</p>
|
||||||
|
<div class="badge bg-warning">Recommended</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Setup Instructions -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h3 class="mb-0"><i class="fas fa-wrench me-2"></i>Setup Instructions</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Step 1: Ensure Server Running -->
|
||||||
|
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
|
||||||
|
<div class="bg-primary rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>1</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Ensure Claude Code Tracker is Running</h5>
|
||||||
|
<p class="text-muted mb-3">The tracker server must be running to receive hook notifications.</p>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mb-3">
|
||||||
|
<h6 class="text-light"><i class="fas fa-terminal me-2"></i>Start the server:</h6>
|
||||||
|
<code>python main.py</code>
|
||||||
|
<br><small class="text-muted">Server runs on http://localhost:8000 by default</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Keep the server running while using Claude Code to ensure hooks work properly.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Locate Settings File -->
|
||||||
|
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
|
||||||
|
<div class="bg-warning rounded-circle text-dark d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>2</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Locate Claude Code Settings</h5>
|
||||||
|
<p class="text-muted mb-3">Find your Claude Code configuration file to add hooks.</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<h6><i class="fab fa-apple me-2"></i>macOS</h6>
|
||||||
|
<code class="small">~/.config/claude/settings.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<h6><i class="fab fa-linux me-2"></i>Linux</h6>
|
||||||
|
<code class="small">~/.config/claude/settings.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<h6><i class="fab fa-windows me-2"></i>Windows</h6>
|
||||||
|
<code class="small">%APPDATA%\claude\settings.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
<strong>Note:</strong> If the file doesn't exist, create it with the hook configuration below.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3: Add Hook Configuration -->
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-success rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
|
||||||
|
<strong>3</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h5 class="mb-2">Add Hook Configuration</h5>
|
||||||
|
<p class="text-muted mb-3">Add the hook configuration to your Claude Code settings file.</p>
|
||||||
|
|
||||||
|
<div class="nav nav-tabs" id="configTabs" role="tablist">
|
||||||
|
<button class="nav-link active" id="basic-tab" data-bs-toggle="tab" data-bs-target="#basic" type="button">
|
||||||
|
<i class="fas fa-rocket me-1"></i>Basic Setup
|
||||||
|
</button>
|
||||||
|
<button class="nav-link" id="advanced-tab" data-bs-toggle="tab" data-bs-target="#advanced" type="button">
|
||||||
|
<i class="fas fa-cogs me-1"></i>Advanced Setup
|
||||||
|
</button>
|
||||||
|
<button class="nav-link" id="custom-tab" data-bs-toggle="tab" data-bs-target="#custom" type="button">
|
||||||
|
<i class="fas fa-edit me-1"></i>Custom Port
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="configTabsContent">
|
||||||
|
<!-- Basic Setup -->
|
||||||
|
<div class="tab-pane fade show active" id="basic" role="tabpanel">
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="text-light mb-0">Basic Configuration</h6>
|
||||||
|
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('basic-config')">
|
||||||
|
<i class="fas fa-copy me-1"></i>Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre id="basic-config" class="mb-0"><code>{
|
||||||
|
"hooks": {
|
||||||
|
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
|
||||||
|
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
|
||||||
|
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
|
||||||
|
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME}'"
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Advanced Setup -->
|
||||||
|
<div class="tab-pane fade" id="advanced" role="tabpanel">
|
||||||
|
<div class="bg-dark text-light p-3 rounded mt-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="text-light mb-0">Advanced Configuration</h6>
|
||||||
|
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('advanced-config')">
|
||||||
|
<i class="fas fa-copy me-1"></i>Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre id="advanced-config" class="mb-0"><code>{
|
||||||
|
"hooks": {
|
||||||
|
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
|
||||||
|
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
|
||||||
|
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
|
||||||
|
"file_modified": "curl -X POST http://localhost:8000/api/activities -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
|
||||||
|
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'"
|
||||||
|
},
|
||||||
|
"hook_settings": {
|
||||||
|
"timeout": 5,
|
||||||
|
"retry_count": 3,
|
||||||
|
"async": true
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Port -->
|
||||||
|
<div class="tab-pane fade" id="custom" role="tabpanel">
|
||||||
|
<div class="alert alert-info mt-3">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
If you're running the tracker on a different port, update the URLs accordingly.
|
||||||
|
</div>
|
||||||
|
<div class="bg-dark text-light p-3 rounded">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="text-light mb-0">Custom Port Configuration</h6>
|
||||||
|
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('custom-config')">
|
||||||
|
<i class="fas fa-copy me-1"></i>Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre id="custom-config" class="mb-0"><code>{
|
||||||
|
"hooks": {
|
||||||
|
"session_start": "curl -X POST http://localhost:YOUR_PORT/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
|
||||||
|
"session_end": "curl -X POST http://localhost:YOUR_PORT/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
|
||||||
|
"conversation_update": "curl -X POST http://localhost:YOUR_PORT/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
|
||||||
|
"tool_call": "curl -X POST http://localhost:YOUR_PORT/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\"}'"
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
<small class="text-muted">Replace YOUR_PORT with your actual port number</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Available Variables -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-dollar-sign me-2 text-info"></i>Available Variables</h3>
|
||||||
|
<p class="text-muted mb-4">Claude Code provides these variables that you can use in your hook commands:</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6><i class="fas fa-clock text-primary me-2"></i>Session Variables</h6>
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$SESSION_ID</code>
|
||||||
|
<br><small class="text-muted">Unique session identifier</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$TIMESTAMP</code>
|
||||||
|
<br><small class="text-muted">Current timestamp (ISO format)</small>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>$PWD</code>
|
||||||
|
<br><small class="text-muted">Current working directory</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card border-0 bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6><i class="fas fa-user text-success me-2"></i>Context Variables</h6>
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$USER</code>
|
||||||
|
<br><small class="text-muted">Current system user</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$CONTENT</code>
|
||||||
|
<br><small class="text-muted">Conversation content</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$FILE_PATH</code>
|
||||||
|
<br><small class="text-muted">Modified file path</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$TOOL_NAME</code>
|
||||||
|
<br><small class="text-muted">Name of tool being called</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<code>$TOOL_PARAMS</code>
|
||||||
|
<br><small class="text-muted">Tool parameters (JSON object)</small>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>$RESULT_STATUS</code>
|
||||||
|
<br><small class="text-muted">Success/error status</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Testing Hooks -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3><i class="fas fa-vial me-2 text-warning"></i>Testing Your Hook Setup</h3>
|
||||||
|
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
<strong>Quick Test:</strong> Start a new Claude Code session after setting up hooks. You should see new data appearing in the tracker dashboard.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Manual Testing</h5>
|
||||||
|
<p>You can test individual hooks manually to ensure they work:</p>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mb-3">
|
||||||
|
<h6 class="text-light">Test session start hook:</h6>
|
||||||
|
<code>curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{"project_path": "/path/to/project", "start_time": "2024-01-01T12:00:00"}'</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-dark text-light p-3 rounded mb-3">
|
||||||
|
<h6 class="text-light">Check server logs:</h6>
|
||||||
|
<code>tail -f logs/tracker.log</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="mt-4">Troubleshooting</h5>
|
||||||
|
<div class="accordion" id="testingAccordion">
|
||||||
|
<div class="accordion-item border-0">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest1">
|
||||||
|
Hook commands not executing
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseTest1" class="accordion-collapse collapse" data-bs-parent="#testingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ul>
|
||||||
|
<li>Check that curl is installed on your system</li>
|
||||||
|
<li>Verify the tracker server is running</li>
|
||||||
|
<li>Test the hook URL manually in a browser or with curl</li>
|
||||||
|
<li>Check Claude Code logs for hook execution errors</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="accordion-item border-0">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest2">
|
||||||
|
Data not appearing in tracker
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseTest2" class="accordion-collapse collapse" data-bs-parent="#testingAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ul>
|
||||||
|
<li>Check the tracker server logs for incoming requests</li>
|
||||||
|
<li>Verify the JSON payload format matches API expectations</li>
|
||||||
|
<li>Ensure the database is writable</li>
|
||||||
|
<li>Try refreshing the dashboard after a few minutes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="sticky-top" style="top: 2rem;">
|
||||||
|
<!-- Quick Reference -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h5 class="mb-0"><i class="fas fa-bolt me-2"></i>Quick Reference</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h6>Default Server URL</h6>
|
||||||
|
<code class="d-block mb-3 p-2 bg-light rounded">http://localhost:8000</code>
|
||||||
|
|
||||||
|
<h6>Settings File Location</h6>
|
||||||
|
<div class="small">
|
||||||
|
<div class="mb-2"><strong>macOS/Linux:</strong></div>
|
||||||
|
<code class="d-block mb-2 p-2 bg-light rounded small">~/.config/claude/settings.json</code>
|
||||||
|
<div class="mb-2"><strong>Windows:</strong></div>
|
||||||
|
<code class="d-block p-2 bg-light rounded small">%APPDATA%\claude\settings.json</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table of Contents -->
|
||||||
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-list me-2"></i>Contents</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<nav class="nav nav-pills flex-column">
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#overview">What are hooks?</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#types">Hook types</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#setup">Setup instructions</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#variables">Available variables</a>
|
||||||
|
<a class="nav-link py-1 px-2 text-sm" href="#testing">Testing hooks</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Help -->
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-life-ring me-2"></i>Need Help?</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="small text-muted mb-3">Still having trouble with hooks?</p>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/dashboard/docs/faq" class="btn btn-outline-warning btn-sm">
|
||||||
|
<i class="fas fa-question-circle me-1"></i>
|
||||||
|
Check FAQ
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/docs/api-reference" class="btn btn-outline-info btn-sm">
|
||||||
|
<i class="fas fa-code me-1"></i>
|
||||||
|
API Reference
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyToClipboard(elementId) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
const text = element.textContent;
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
|
// Show success feedback
|
||||||
|
const button = element.parentElement.querySelector('button');
|
||||||
|
const originalText = button.innerHTML;
|
||||||
|
button.innerHTML = '<i class="fas fa-check me-1"></i>Copied!';
|
||||||
|
button.classList.add('btn-success');
|
||||||
|
button.classList.remove('btn-outline-light');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
button.classList.remove('btn-success');
|
||||||
|
button.classList.add('btn-outline-light');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
293
app/dashboard/templates/docs/index.html
Normal file
293
app/dashboard/templates/docs/index.html
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2><i class="fas fa-book me-3"></i>Documentation</h2>
|
||||||
|
<div class="text-muted">
|
||||||
|
<i class="fas fa-info-circle me-1"></i>
|
||||||
|
Comprehensive guide to using Claude Code Tracker
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Getting Started -->
|
||||||
|
<div class="col-md-6 col-lg-3 mb-4">
|
||||||
|
<div class="card h-100 border-0 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div class="bg-primary bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||||
|
<i class="fas fa-rocket fa-lg text-primary"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-center">Getting Started</h5>
|
||||||
|
<p class="card-text text-center text-muted flex-grow-1">
|
||||||
|
Learn the basics of Claude Code Tracker and get up and running quickly.
|
||||||
|
</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/dashboard/docs/getting-started" class="btn btn-primary">
|
||||||
|
<i class="fas fa-arrow-right me-1"></i>
|
||||||
|
Read Guide
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hook Setup -->
|
||||||
|
<div class="col-md-6 col-lg-3 mb-4">
|
||||||
|
<div class="card h-100 border-0 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div class="bg-success bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||||
|
<i class="fas fa-link fa-lg text-success"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-center">Hook Setup Guide</h5>
|
||||||
|
<p class="card-text text-center text-muted flex-grow-1">
|
||||||
|
Configure Claude Code hooks to automatically track your development activity.
|
||||||
|
</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/dashboard/docs/hook-setup" class="btn btn-success">
|
||||||
|
<i class="fas fa-arrow-right me-1"></i>
|
||||||
|
Setup Hooks
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- API Reference -->
|
||||||
|
<div class="col-md-6 col-lg-3 mb-4">
|
||||||
|
<div class="card h-100 border-0 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div class="bg-info bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||||
|
<i class="fas fa-code fa-lg text-info"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-center">API Reference</h5>
|
||||||
|
<p class="card-text text-center text-muted flex-grow-1">
|
||||||
|
Detailed API documentation for integrating with Claude Code Tracker.
|
||||||
|
</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/dashboard/docs/api-reference" class="btn btn-info">
|
||||||
|
<i class="fas fa-arrow-right me-1"></i>
|
||||||
|
View API
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FAQ -->
|
||||||
|
<div class="col-md-6 col-lg-3 mb-4">
|
||||||
|
<div class="card h-100 border-0 shadow-sm">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<div class="bg-warning bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
||||||
|
<i class="fas fa-question-circle fa-lg text-warning"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title text-center">FAQ</h5>
|
||||||
|
<p class="card-text text-center text-muted flex-grow-1">
|
||||||
|
Frequently asked questions and troubleshooting guide.
|
||||||
|
</p>
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/dashboard/docs/faq" class="btn btn-warning">
|
||||||
|
<i class="fas fa-arrow-right me-1"></i>
|
||||||
|
Get Help
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Start Section -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-header bg-light">
|
||||||
|
<h4 class="mb-0"><i class="fas fa-lightning-bolt me-2"></i>Quick Start</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<p class="lead">Get started with Claude Code Tracker in just a few steps:</p>
|
||||||
|
<ol class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge bg-primary rounded-pill me-2">1</span>
|
||||||
|
<strong>Import existing data</strong> from your <code>~/.claude.json</code> file
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge bg-primary rounded-pill me-2">2</span>
|
||||||
|
<strong>Set up hooks</strong> to automatically track new sessions
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge bg-primary rounded-pill me-2">3</span>
|
||||||
|
<strong>Explore your data</strong> through the dashboard and analytics
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/dashboard/import" class="btn btn-success btn-lg">
|
||||||
|
<i class="fas fa-upload me-2"></i>
|
||||||
|
Import Data Now
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/docs/getting-started" class="btn btn-outline-primary">
|
||||||
|
<i class="fas fa-book me-2"></i>
|
||||||
|
Read Full Guide
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Features Overview -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3 class="mb-4"><i class="fas fa-star me-2"></i>Key Features</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-primary bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-chart-line text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Development Analytics</h6>
|
||||||
|
<p class="card-text small text-muted">Track sessions, time spent, and productivity trends across all your projects.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-success bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-comments text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Conversation History</h6>
|
||||||
|
<p class="card-text small text-muted">Search and browse through all your Claude Code conversations with powerful filtering.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-info bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-timeline text-info"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Project Timelines</h6>
|
||||||
|
<p class="card-text small text-muted">Visualize your development journey with chronological project timelines.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-warning bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-link text-warning"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Hook Integration</h6>
|
||||||
|
<p class="card-text small text-muted">Automatically capture data through Claude Code hooks for real-time tracking.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-secondary bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-database text-secondary"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Data Import/Export</h6>
|
||||||
|
<p class="card-text small text-muted">Import existing data from Claude.json and export your analytics.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card border-0 bg-light h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="bg-danger bg-opacity-10 rounded p-2 me-3">
|
||||||
|
<i class="fas fa-shield-alt text-danger"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="card-title">Privacy First</h6>
|
||||||
|
<p class="card-text small text-muted">All data stays local - no external services or cloud dependencies.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- External Links -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card border-0 bg-primary text-white">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h5 class="card-title mb-2">
|
||||||
|
<i class="fas fa-external-link-alt me-2"></i>
|
||||||
|
Additional Resources
|
||||||
|
</h5>
|
||||||
|
<p class="card-text mb-0">
|
||||||
|
Explore the interactive API documentation and source code
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-end">
|
||||||
|
<a href="/docs" target="_blank" class="btn btn-light me-2">
|
||||||
|
<i class="fas fa-book me-1"></i>
|
||||||
|
API Docs
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/claude/claude-tracker" target="_blank" class="btn btn-outline-light">
|
||||||
|
<i class="fab fa-github me-1"></i>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
313
app/dashboard/templates/import.html
Normal file
313
app/dashboard/templates/import.html
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Import Data - Claude Code Project Tracker{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>
|
||||||
|
<i class="fas fa-download me-2"></i>
|
||||||
|
Import Claude Code Data
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-file-import me-2"></i>
|
||||||
|
Import from .claude.json
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="text-muted">
|
||||||
|
Import your historical Claude Code usage data from the <code>~/.claude.json</code> file.
|
||||||
|
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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<!-- Results -->
|
||||||
|
<div id="import-results" class="mt-4" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<div id="results-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
What gets imported?
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-folder text-primary me-2"></i>
|
||||||
|
<strong>Projects</strong><br>
|
||||||
|
<small class="text-muted">All project directories you've used with Claude Code</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-chart-line text-success me-2"></i>
|
||||||
|
<strong>Usage Statistics</strong><br>
|
||||||
|
<small class="text-muted">Total startups and estimated session distribution</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-comments text-info me-2"></i>
|
||||||
|
<strong>History Entries</strong><br>
|
||||||
|
<small class="text-muted">Brief conversation topics from your history</small>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fas fa-code text-warning me-2"></i>
|
||||||
|
<strong>Language Detection</strong><br>
|
||||||
|
<small class="text-muted">Automatically detect programming languages used</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-shield-alt me-1"></i>
|
||||||
|
<strong>Privacy:</strong> All data stays local. No external services are used.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||||
|
Important Notes
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="small text-muted">
|
||||||
|
<li>Import creates estimated data based on available information</li>
|
||||||
|
<li>Timestamps are estimated based on usage patterns</li>
|
||||||
|
<li>This is safe to run multiple times (won't create duplicates)</li>
|
||||||
|
<li>Large files may take a few moments to process</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
async function previewImport() {
|
||||||
|
const filePath = document.getElementById('file-path').value.trim();
|
||||||
|
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 .claude.json file...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultsDiv.style.display = 'block';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (filePath) {
|
||||||
|
params.append('file_path', filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`/api/import/claude-json/preview?${params}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showPreviewResults(data);
|
||||||
|
} else {
|
||||||
|
showError(data.detail || 'Preview failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError('Network error: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runImport() {
|
||||||
|
const filePath = document.getElementById('file-path').value.trim();
|
||||||
|
const resultsDiv = document.getElementById('import-results');
|
||||||
|
const resultsContent = document.getElementById('results-content');
|
||||||
|
|
||||||
|
// Confirm import
|
||||||
|
if (!confirm('This will import data into your tracker database. Continue?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Claude Code data...</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
resultsDiv.style.display = 'block';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestBody = {};
|
||||||
|
if (filePath) {
|
||||||
|
requestBody.file_path = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/import/claude-json', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
showImportResults(data);
|
||||||
|
} else {
|
||||||
|
showError(data.detail || 'Import failed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError('Network error: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPreviewResults(data) {
|
||||||
|
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>Size:</strong> ${data.file_size_mb} MB</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Projects:</strong> ${data.projects.total_count}</p>
|
||||||
|
<p><strong>History entries:</strong> ${data.history_entries}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<strong>Claude Usage Statistics:</strong>
|
||||||
|
<ul class="mt-2">
|
||||||
|
<li>Total startups: ${data.claude_usage.num_startups}</li>
|
||||||
|
<li>First start: ${data.claude_usage.first_start_time || 'N/A'}</li>
|
||||||
|
<li>Prompt queue uses: ${data.claude_usage.prompt_queue_use_count}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${data.projects.paths.length > 0 ? `
|
||||||
|
<div class="mt-3">
|
||||||
|
<strong>Sample projects:</strong>
|
||||||
|
<ul class="mt-2">
|
||||||
|
${data.projects.paths.map(path => `<li><code>${path}</code></li>`).join('')}
|
||||||
|
${data.projects.has_more ? '<li><em>... and more</em></li>' : ''}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<button class="btn btn-success" onclick="runImport()">
|
||||||
|
<i class="fas fa-check me-1"></i>
|
||||||
|
Looks good - Import this data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('results-content').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showImportResults(data) {
|
||||||
|
const results = data.results;
|
||||||
|
const hasErrors = results.errors && results.errors.length > 0;
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<h6><i class="fas fa-check me-1"></i> Import Completed Successfully!</h6>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<div class="display-6 text-primary">${results.projects_imported}</div>
|
||||||
|
<small>Projects</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<div class="display-6 text-success">${results.sessions_estimated}</div>
|
||||||
|
<small>Sessions</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<div class="display-6 text-info">${results.conversations_imported}</div>
|
||||||
|
<small>Conversations</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${hasErrors ? `
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<h6><i class="fas fa-exclamation-triangle me-1"></i> Warnings</h6>
|
||||||
|
<ul class="mb-0">
|
||||||
|
${results.errors.map(error => `<li>${error}</li>`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="/dashboard" class="btn btn-primary">
|
||||||
|
<i class="fas fa-tachometer-alt me-1"></i>
|
||||||
|
View Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="/dashboard/projects" class="btn btn-outline-primary ms-2">
|
||||||
|
<i class="fas fa-folder me-1"></i>
|
||||||
|
View Projects
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('results-content').innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
const html = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<h6><i class="fas fa-exclamation-circle me-1"></i> Error</h6>
|
||||||
|
<p class="mb-0">${message}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById('results-content').innerHTML = html;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
353
app/dashboard/templates/project_stats.html
Normal file
353
app/dashboard/templates/project_stats.html
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Project Statistics</h2>
|
||||||
|
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="project-info" class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Loading project information...</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Time Period</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<select id="dayRange" class="form-select">
|
||||||
|
<option value="7">Last 7 days</option>
|
||||||
|
<option value="30" selected>Last 30 days</option>
|
||||||
|
<option value="90">Last 90 days</option>
|
||||||
|
<option value="365">Last year</option>
|
||||||
|
<option value="0">All time</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Summary</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="summary-stats">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Session Statistics</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="session-stats">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Activity Breakdown</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="activity-stats">
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Tool Usage</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="toolsChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Language Usage</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="languagesChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Productivity Trends</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="trendsChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script>
|
||||||
|
const projectId = {{ project_id }};
|
||||||
|
let statsData = {};
|
||||||
|
let charts = {};
|
||||||
|
|
||||||
|
async function loadProjectStats() {
|
||||||
|
try {
|
||||||
|
const days = document.getElementById('dayRange').value;
|
||||||
|
const response = await fetch(`/api/projects/${projectId}/stats?days=${days}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
statsData = data;
|
||||||
|
displayStats();
|
||||||
|
updateCharts();
|
||||||
|
} else {
|
||||||
|
throw new Error(data.detail || 'Failed to load stats');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading stats:', error);
|
||||||
|
document.getElementById('summary-stats').innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
Failed to load statistics: ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProjectInfo() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/projects/${projectId}`);
|
||||||
|
const project = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
document.getElementById('project-info').innerHTML = `
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">${project.name}</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Path:</strong> ${project.path}<br>
|
||||||
|
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
|
||||||
|
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading project info:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayStats() {
|
||||||
|
// Summary stats
|
||||||
|
document.getElementById('summary-stats').innerHTML = `
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4 class="text-primary">${statsData.session_statistics.total_sessions}</h4>
|
||||||
|
<small>Sessions</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4 class="text-success">${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10}h</h4>
|
||||||
|
<small>Total Time</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4 class="text-warning">${statsData.activity_statistics.total}</h4>
|
||||||
|
<small>Activities</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4 class="text-info">${statsData.conversation_statistics.total}</h4>
|
||||||
|
<small>Conversations</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Session stats
|
||||||
|
document.getElementById('session-stats').innerHTML = `
|
||||||
|
<p><strong>Total Sessions:</strong> ${statsData.session_statistics.total_sessions}</p>
|
||||||
|
<p><strong>Total Time:</strong> ${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10} hours</p>
|
||||||
|
<p><strong>Average Session:</strong> ${statsData.session_statistics.average_session_length_minutes} minutes</p>
|
||||||
|
<p><strong>Daily Average Time:</strong> ${statsData.summary.daily_average_time} minutes</p>
|
||||||
|
<p><strong>Daily Average Sessions:</strong> ${statsData.summary.daily_average_sessions}</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Activity stats
|
||||||
|
const topTools = Object.entries(statsData.activity_statistics.by_tool)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([tool, count]) => `<li>${tool}: ${count}</li>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const topLangs = Object.entries(statsData.activity_statistics.by_language)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([lang, count]) => `<li>${lang}: ${count}</li>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
document.getElementById('activity-stats').innerHTML = `
|
||||||
|
<p><strong>Total Activities:</strong> ${statsData.activity_statistics.total}</p>
|
||||||
|
<p><strong>Most Used Tool:</strong> ${statsData.summary.most_used_tool || 'None'}</p>
|
||||||
|
<p><strong>Primary Language:</strong> ${statsData.summary.primary_language || 'None'}</p>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Top Tools:</strong>
|
||||||
|
<ul class="small">${topTools || '<li>No data</li>'}</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Languages:</strong>
|
||||||
|
<ul class="small">${topLangs || '<li>No data</li>'}</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCharts() {
|
||||||
|
// Destroy existing charts
|
||||||
|
Object.values(charts).forEach(chart => chart.destroy());
|
||||||
|
charts = {};
|
||||||
|
|
||||||
|
// Tools chart
|
||||||
|
const toolData = Object.entries(statsData.activity_statistics.by_tool)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
if (toolData.length > 0) {
|
||||||
|
charts.tools = new Chart(document.getElementById('toolsChart'), {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: toolData.map(([tool]) => tool),
|
||||||
|
datasets: [{
|
||||||
|
data: toolData.map(([, count]) => count),
|
||||||
|
backgroundColor: [
|
||||||
|
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
|
||||||
|
'#FF9F40', '#C9CBCF', '#4BC0C0', '#FF6384', '#36A2EB'
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Languages chart
|
||||||
|
const langData = Object.entries(statsData.activity_statistics.by_language)
|
||||||
|
.sort((a, b) => b[1] - a[1]);
|
||||||
|
|
||||||
|
if (langData.length > 0) {
|
||||||
|
charts.languages = new Chart(document.getElementById('languagesChart'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: langData.map(([lang]) => lang),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Activities',
|
||||||
|
data: langData.map(([, count]) => count),
|
||||||
|
backgroundColor: '#36A2EB'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trends chart
|
||||||
|
if (statsData.productivity_trends && statsData.productivity_trends.length > 0) {
|
||||||
|
charts.trends = new Chart(document.getElementById('trendsChart'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: statsData.productivity_trends.map(day => day.date),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Sessions',
|
||||||
|
data: statsData.productivity_trends.map(day => day.sessions),
|
||||||
|
borderColor: '#FF6384',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
||||||
|
yAxisID: 'y'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Time (minutes)',
|
||||||
|
data: statsData.productivity_trends.map(day => day.time_minutes),
|
||||||
|
borderColor: '#36A2EB',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||||||
|
yAxisID: 'y1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
display: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Date'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'left',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Sessions'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y1: {
|
||||||
|
type: 'linear',
|
||||||
|
display: true,
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Time (minutes)'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
drawOnChartArea: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
document.getElementById('dayRange').addEventListener('change', loadProjectStats);
|
||||||
|
|
||||||
|
// Load initial data
|
||||||
|
loadProjectInfo();
|
||||||
|
loadProjectStats();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
214
app/dashboard/templates/project_timeline.html
Normal file
214
app/dashboard/templates/project_timeline.html
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Project Timeline</h2>
|
||||||
|
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="project-info" class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Loading project information...</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5>Timeline Events</h5>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input type="date" id="startDate" class="form-control" placeholder="Start Date">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input type="date" id="endDate" class="form-control" placeholder="End Date">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="timeline-container" class="timeline">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const projectId = {{ project_id }};
|
||||||
|
let timelineData = [];
|
||||||
|
|
||||||
|
async function loadProjectTimeline() {
|
||||||
|
try {
|
||||||
|
const startDate = document.getElementById('startDate').value;
|
||||||
|
const endDate = document.getElementById('endDate').value;
|
||||||
|
|
||||||
|
let url = `/api/projects/${projectId}/timeline`;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (startDate) params.append('start_date', startDate);
|
||||||
|
if (endDate) params.append('end_date', endDate);
|
||||||
|
if (params.toString()) url += '?' + params.toString();
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
timelineData = data;
|
||||||
|
displayProjectInfo(data.project);
|
||||||
|
displayTimeline(data.timeline);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.detail || 'Failed to load timeline');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading timeline:', error);
|
||||||
|
document.getElementById('timeline-container').innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
Failed to load timeline: ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayProjectInfo(project) {
|
||||||
|
document.getElementById('project-info').innerHTML = `
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">${project.name}</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Path:</strong> ${project.path}<br>
|
||||||
|
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
|
||||||
|
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}<br>
|
||||||
|
<strong>Total Sessions:</strong> ${project.total_sessions}<br>
|
||||||
|
<strong>Total Time:</strong> ${project.total_time_minutes} minutes<br>
|
||||||
|
<strong>Last Activity:</strong> ${new Date(project.last_activity).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayTimeline(timeline) {
|
||||||
|
if (!timeline || timeline.length === 0) {
|
||||||
|
document.getElementById('timeline-container').innerHTML = `
|
||||||
|
<div class="alert alert-info">No timeline events found for the selected period.</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timelineHtml = timeline.map(event => {
|
||||||
|
const timestamp = new Date(event.timestamp).toLocaleString();
|
||||||
|
const typeClass = getEventTypeClass(event.type);
|
||||||
|
const icon = getEventIcon(event.type);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="timeline-item mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2 text-end">
|
||||||
|
<small class="text-muted">${timestamp}</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 text-center">
|
||||||
|
<span class="badge ${typeClass}">${icon}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="card border-start border-3 border-${typeClass.replace('bg-', '')}">
|
||||||
|
<div class="card-body py-2">
|
||||||
|
<h6 class="card-title mb-1">${formatEventTitle(event)}</h6>
|
||||||
|
<p class="card-text small text-muted mb-0">${formatEventDetails(event)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
document.getElementById('timeline-container').innerHTML = timelineHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventTypeClass(type) {
|
||||||
|
const classes = {
|
||||||
|
'session_start': 'bg-success',
|
||||||
|
'session_end': 'bg-secondary',
|
||||||
|
'conversation': 'bg-primary',
|
||||||
|
'activity': 'bg-warning',
|
||||||
|
'git_operation': 'bg-info'
|
||||||
|
};
|
||||||
|
return classes[type] || 'bg-light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
'session_start': '▶️',
|
||||||
|
'session_end': '⏹️',
|
||||||
|
'conversation': '💬',
|
||||||
|
'activity': '🔧',
|
||||||
|
'git_operation': '📝'
|
||||||
|
};
|
||||||
|
return icons[type] || '📌';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEventTitle(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'session_start':
|
||||||
|
return `Session Started (${event.data.session_type || 'unknown'})`;
|
||||||
|
case 'session_end':
|
||||||
|
return `Session Ended (${event.data.duration_minutes || 0}m)`;
|
||||||
|
case 'conversation':
|
||||||
|
return `Conversation: ${event.data.exchange_type || 'unknown'}`;
|
||||||
|
case 'activity':
|
||||||
|
return `${event.data.tool_name}: ${event.data.action}`;
|
||||||
|
case 'git_operation':
|
||||||
|
return `Git: ${event.data.operation}`;
|
||||||
|
default:
|
||||||
|
return event.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEventDetails(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'session_start':
|
||||||
|
return `Branch: ${event.data.git_branch || 'unknown'}, Directory: ${event.data.working_directory || 'unknown'}`;
|
||||||
|
case 'session_end':
|
||||||
|
return `Activities: ${event.data.activity_count || 0}, Conversations: ${event.data.conversation_count || 0}`;
|
||||||
|
case 'conversation':
|
||||||
|
return event.data.user_prompt || 'No prompt available';
|
||||||
|
case 'activity':
|
||||||
|
return `File: ${event.data.file_path || 'unknown'}, Lines: ${event.data.lines_changed || 0}, Success: ${event.data.success}`;
|
||||||
|
case 'git_operation':
|
||||||
|
return `${event.data.commit_message || 'No message'} (${event.data.files_changed || 0} files, ${event.data.lines_changed || 0} lines)`;
|
||||||
|
default:
|
||||||
|
return JSON.stringify(event.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
document.getElementById('startDate').addEventListener('change', loadProjectTimeline);
|
||||||
|
document.getElementById('endDate').addEventListener('change', loadProjectTimeline);
|
||||||
|
|
||||||
|
// Load initial data
|
||||||
|
loadProjectTimeline();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-success { border-color: #198754 !important; }
|
||||||
|
.border-secondary { border-color: #6c757d !important; }
|
||||||
|
.border-primary { border-color: #0d6efd !important; }
|
||||||
|
.border-warning { border-color: #ffc107 !important; }
|
||||||
|
.border-info { border-color: #0dcaf0 !important; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
@ -56,7 +56,7 @@ async def init_database():
|
|||||||
# Import all models to ensure they're registered
|
# Import all models to ensure they're registered
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Project, Session, Conversation, Activity,
|
Project, Session, Conversation, Activity,
|
||||||
WaitingPeriod, GitOperation
|
WaitingPeriod, GitOperation, ToolCall
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create all tables
|
# Create all tables
|
||||||
|
@ -9,6 +9,7 @@ from .conversation import Conversation
|
|||||||
from .activity import Activity
|
from .activity import Activity
|
||||||
from .waiting_period import WaitingPeriod
|
from .waiting_period import WaitingPeriod
|
||||||
from .git_operation import GitOperation
|
from .git_operation import GitOperation
|
||||||
|
from .tool_call import ToolCall
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Base",
|
"Base",
|
||||||
@ -18,4 +19,5 @@ __all__ = [
|
|||||||
"Activity",
|
"Activity",
|
||||||
"WaitingPeriod",
|
"WaitingPeriod",
|
||||||
"GitOperation",
|
"GitOperation",
|
||||||
|
"ToolCall",
|
||||||
]
|
]
|
@ -46,7 +46,7 @@ class Activity(Base, TimestampMixin):
|
|||||||
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
|
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
|
||||||
|
|
||||||
# Metadata and results
|
# Metadata and results
|
||||||
metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
|
activity_metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
|
||||||
success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||||||
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
|
|
||||||
@ -145,18 +145,18 @@ class Activity(Base, TimestampMixin):
|
|||||||
|
|
||||||
def get_command_executed(self) -> Optional[str]:
|
def get_command_executed(self) -> Optional[str]:
|
||||||
"""Get the command that was executed (for Bash activities)."""
|
"""Get the command that was executed (for Bash activities)."""
|
||||||
if self.tool_name == "Bash" and self.metadata:
|
if self.tool_name == "Bash" and self.activity_metadata:
|
||||||
return self.metadata.get("command")
|
return self.activity_metadata.get("command")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_search_pattern(self) -> Optional[str]:
|
def get_search_pattern(self) -> Optional[str]:
|
||||||
"""Get the search pattern (for Grep activities)."""
|
"""Get the search pattern (for Grep activities)."""
|
||||||
if self.tool_name == "Grep" and self.metadata:
|
if self.tool_name == "Grep" and self.activity_metadata:
|
||||||
return self.metadata.get("pattern")
|
return self.activity_metadata.get("pattern")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_task_type(self) -> Optional[str]:
|
def get_task_type(self) -> Optional[str]:
|
||||||
"""Get the task type (for Task activities)."""
|
"""Get the task type (for Task activities)."""
|
||||||
if self.tool_name == "Task" and self.metadata:
|
if self.tool_name == "Task" and self.activity_metadata:
|
||||||
return self.metadata.get("task_type")
|
return self.activity_metadata.get("task_type")
|
||||||
return None
|
return None
|
@ -36,6 +36,7 @@ class Project(Base, TimestampMixin):
|
|||||||
total_time_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
total_time_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||||
files_modified_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
files_modified_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||||
lines_changed_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
lines_changed_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||||
|
tool_calls_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
sessions: Mapped[List["Session"]] = relationship(
|
sessions: Mapped[List["Session"]] = relationship(
|
||||||
@ -60,10 +61,11 @@ class Project(Base, TimestampMixin):
|
|||||||
return self.languages[0]
|
return self.languages[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int) -> None:
|
def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int, tool_calls_count: int = 0) -> None:
|
||||||
"""Update project statistics after a session."""
|
"""Update project statistics after a session."""
|
||||||
self.total_sessions += 1
|
self.total_sessions += 1
|
||||||
self.total_time_minutes += session_duration_minutes
|
self.total_time_minutes += session_duration_minutes
|
||||||
self.files_modified_count += files_count
|
self.files_modified_count += files_count
|
||||||
self.lines_changed_count += lines_count
|
self.lines_changed_count += lines_count
|
||||||
|
self.tool_calls_count += tool_calls_count
|
||||||
self.last_session = func.now()
|
self.last_session = func.now()
|
@ -74,6 +74,12 @@ class Session(Base, TimestampMixin):
|
|||||||
cascade="all, delete-orphan",
|
cascade="all, delete-orphan",
|
||||||
order_by="GitOperation.timestamp"
|
order_by="GitOperation.timestamp"
|
||||||
)
|
)
|
||||||
|
tool_calls: Mapped[List["ToolCall"]] = relationship(
|
||||||
|
"ToolCall",
|
||||||
|
back_populates="session",
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
order_by="ToolCall.timestamp"
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Session(id={self.id}, project_id={self.project_id}, type='{self.session_type}')>"
|
return f"<Session(id={self.id}, project_id={self.project_id}, type='{self.session_type}')>"
|
||||||
@ -111,10 +117,12 @@ class Session(Base, TimestampMixin):
|
|||||||
(activity.lines_added or 0) + (activity.lines_removed or 0)
|
(activity.lines_added or 0) + (activity.lines_removed or 0)
|
||||||
for activity in self.activities
|
for activity in self.activities
|
||||||
)
|
)
|
||||||
|
total_tool_calls = len(self.tool_calls)
|
||||||
self.project.update_stats(
|
self.project.update_stats(
|
||||||
session_duration_minutes=self.duration_minutes or 0,
|
session_duration_minutes=self.duration_minutes or 0,
|
||||||
files_count=unique_files,
|
files_count=unique_files,
|
||||||
lines_count=total_lines
|
lines_count=total_lines,
|
||||||
|
tool_calls_count=total_tool_calls
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_activity(self) -> None:
|
def add_activity(self) -> None:
|
||||||
|
33
app/models/tool_call.py
Normal file
33
app/models/tool_call.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Tool call tracking model for Claude Code sessions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.models.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class ToolCall(Base):
|
||||||
|
"""
|
||||||
|
Tracks individual tool calls made during Claude Code sessions.
|
||||||
|
|
||||||
|
This helps analyze which tools are used most frequently and their success rates.
|
||||||
|
"""
|
||||||
|
__tablename__ = "tool_calls"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
|
||||||
|
tool_name = Column(String(100), nullable=False, index=True)
|
||||||
|
parameters = Column(Text, nullable=True) # JSON string of tool parameters
|
||||||
|
result_status = Column(String(20), nullable=True, index=True) # success, error, timeout
|
||||||
|
error_message = Column(Text, nullable=True)
|
||||||
|
execution_time_ms = Column(Integer, nullable=True) # Tool execution time in milliseconds
|
||||||
|
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
session = relationship("Session", back_populates="tool_calls")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ToolCall(id={self.id}, tool={self.tool_name}, session={self.session_id})>"
|
154
import_claude_data.py
Normal file
154
import_claude_data.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Command-line tool to import data from .claude.json file.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python import_claude_data.py [--file path/to/.claude.json] [--preview]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from app.database.connection import async_session_maker, init_database
|
||||||
|
from app.api.importer import ClaudeJsonImporter
|
||||||
|
|
||||||
|
|
||||||
|
async def preview_import(file_path: str):
|
||||||
|
"""Preview what would be imported."""
|
||||||
|
print(f"🔍 Previewing import from: {file_path}")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
print(f"❌ File not found: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
claude_data = json.load(f)
|
||||||
|
|
||||||
|
file_size_mb = round(os.path.getsize(file_path) / (1024 * 1024), 2)
|
||||||
|
|
||||||
|
print(f"📁 File size: {file_size_mb} MB")
|
||||||
|
print(f"🚀 Total Claude Code startups: {claude_data.get('numStartups', 0)}")
|
||||||
|
print(f"📅 First start time: {claude_data.get('firstStartTime', 'N/A')}")
|
||||||
|
print(f"📊 Prompt queue uses: {claude_data.get('promptQueueUseCount', 0)}")
|
||||||
|
|
||||||
|
if "projects" in claude_data:
|
||||||
|
projects = claude_data["projects"]
|
||||||
|
print(f"📂 Projects found: {len(projects)}")
|
||||||
|
|
||||||
|
# Show first few project paths
|
||||||
|
paths = list(projects.keys())[:5]
|
||||||
|
for path in paths:
|
||||||
|
history_count = len(projects[path].get("history", []))
|
||||||
|
print(f" • {path} ({history_count} history entries)")
|
||||||
|
|
||||||
|
if len(projects) > 5:
|
||||||
|
print(f" ... and {len(projects) - 5} more projects")
|
||||||
|
|
||||||
|
# Count total history entries
|
||||||
|
total_history = sum(len(proj.get("history", [])) for proj in projects.values())
|
||||||
|
print(f"💬 Total conversation history entries: {total_history}")
|
||||||
|
|
||||||
|
print("\n✅ Preview completed successfully!")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Preview failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def run_import(file_path: str):
|
||||||
|
"""Run the actual import process."""
|
||||||
|
print(f"📥 Starting import from: {file_path}")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Initialize database tables first
|
||||||
|
print("🔧 Initializing database...")
|
||||||
|
await init_database()
|
||||||
|
print("✅ Database initialized")
|
||||||
|
|
||||||
|
async with async_session_maker() as db:
|
||||||
|
try:
|
||||||
|
importer = ClaudeJsonImporter(db)
|
||||||
|
results = await importer.import_from_file(file_path)
|
||||||
|
|
||||||
|
print("🎉 Import completed successfully!")
|
||||||
|
print(f"📂 Projects imported: {results['projects_imported']}")
|
||||||
|
print(f"⏱️ Sessions estimated: {results['sessions_estimated']}")
|
||||||
|
print(f"💬 Conversations imported: {results['conversations_imported']}")
|
||||||
|
|
||||||
|
if results['errors']:
|
||||||
|
print(f"\n⚠️ Warnings/Errors ({len(results['errors'])}):")
|
||||||
|
for error in results['errors']:
|
||||||
|
print(f" • {error}")
|
||||||
|
|
||||||
|
print("\n🚀 You can now view your imported data at: http://localhost:8000")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Import failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Import Claude Code data from .claude.json file",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
Examples:
|
||||||
|
python import_claude_data.py --preview
|
||||||
|
python import_claude_data.py --file ~/.claude.json
|
||||||
|
python import_claude_data.py --file ~/.claude.json --preview
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--file",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Path to .claude.json file (default: ~/.claude.json)"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--preview",
|
||||||
|
action="store_true",
|
||||||
|
help="Preview what would be imported without actually importing"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Determine file path
|
||||||
|
if args.file:
|
||||||
|
file_path = args.file
|
||||||
|
else:
|
||||||
|
file_path = str(Path.home() / ".claude.json")
|
||||||
|
|
||||||
|
print("🤖 Claude Code Project Tracker - Data Importer")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
if args.preview:
|
||||||
|
success = asyncio.run(preview_import(file_path))
|
||||||
|
else:
|
||||||
|
print("⚠️ This will import data into your Claude Code Project Tracker database.")
|
||||||
|
print(" Make sure the tracker server is not running during import.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
confirm = input("Continue with import? (y/N): ").lower().strip()
|
||||||
|
if confirm != 'y':
|
||||||
|
print("Import cancelled.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
success = asyncio.run(run_import(file_path))
|
||||||
|
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
4
main.py
4
main.py
@ -9,7 +9,7 @@ from fastapi.staticfiles import StaticFiles
|
|||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
from app.database.connection import init_database, close_database
|
from app.database.connection import init_database, close_database
|
||||||
from app.api import sessions, conversations, activities, waiting, git, projects, analytics
|
from app.api import sessions, conversations, activities, waiting, git, projects, analytics, importer, tool_calls
|
||||||
from app.dashboard.routes import dashboard_router
|
from app.dashboard.routes import dashboard_router
|
||||||
|
|
||||||
|
|
||||||
@ -50,6 +50,8 @@ app.include_router(waiting.router, prefix="/api", tags=["Waiting Periods"])
|
|||||||
app.include_router(git.router, prefix="/api", tags=["Git Operations"])
|
app.include_router(git.router, prefix="/api", tags=["Git Operations"])
|
||||||
app.include_router(projects.router, prefix="/api", tags=["Projects"])
|
app.include_router(projects.router, prefix="/api", tags=["Projects"])
|
||||||
app.include_router(analytics.router, prefix="/api", tags=["Analytics"])
|
app.include_router(analytics.router, prefix="/api", tags=["Analytics"])
|
||||||
|
app.include_router(importer.router, prefix="/api", tags=["Data Import"])
|
||||||
|
app.include_router(tool_calls.router, prefix="/api", tags=["Tool Calls"])
|
||||||
|
|
||||||
# Include dashboard routes
|
# Include dashboard routes
|
||||||
app.include_router(dashboard_router, tags=["Dashboard"])
|
app.include_router(dashboard_router, tags=["Dashboard"])
|
||||||
|
124
pyproject.toml
Normal file
124
pyproject.toml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "claude-code-tracker"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Development intelligence system for tracking Claude Code projects and productivity"
|
||||||
|
authors = [
|
||||||
|
{name = "Claude Code Tracker", email = "claude-code@example.com"}
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Environment :: Web Environment",
|
||||||
|
"Framework :: FastAPI",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
|
"Topic :: Software Development :: Quality Assurance",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"fastapi>=0.104.0",
|
||||||
|
"uvicorn[standard]>=0.24.0",
|
||||||
|
"sqlalchemy>=2.0.20",
|
||||||
|
"aiosqlite>=0.19.0",
|
||||||
|
"pydantic>=2.5.0",
|
||||||
|
"jinja2>=3.1.0",
|
||||||
|
"python-multipart>=0.0.6",
|
||||||
|
"python-jose[cryptography]>=3.3.0",
|
||||||
|
"passlib[bcrypt]>=1.7.4",
|
||||||
|
"python-dateutil>=2.8.0",
|
||||||
|
"typing-extensions>=4.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.4.0",
|
||||||
|
"pytest-asyncio>=0.21.0",
|
||||||
|
"httpx>=0.24.0",
|
||||||
|
"black>=23.0.0",
|
||||||
|
"isort>=5.12.0",
|
||||||
|
"flake8>=6.0.0",
|
||||||
|
"mypy>=1.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://git.supported.systems/claude/claude-code-tracker"
|
||||||
|
Repository = "https://git.supported.systems/claude/claude-code-tracker.git"
|
||||||
|
Issues = "https://git.supported.systems/claude/claude-code-tracker/issues"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
claude-tracker = "main:main"
|
||||||
|
import-claude-data = "import_claude_data:main"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["app"]
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 88
|
||||||
|
target-version = ["py39", "py310", "py311", "py312"]
|
||||||
|
include = '\.pyi?$'
|
||||||
|
extend-exclude = '''
|
||||||
|
/(
|
||||||
|
# directories
|
||||||
|
\.eggs
|
||||||
|
| \.git
|
||||||
|
| \.hg
|
||||||
|
| \.mypy_cache
|
||||||
|
| \.tox
|
||||||
|
| \.venv
|
||||||
|
| build
|
||||||
|
| dist
|
||||||
|
)/
|
||||||
|
'''
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
multi_line_output = 3
|
||||||
|
line_length = 88
|
||||||
|
known_first_party = ["app"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.9"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_no_return = true
|
||||||
|
warn_unreachable = true
|
||||||
|
strict_equality = true
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "tests.*"
|
||||||
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--disable-warnings",
|
||||||
|
]
|
||||||
|
markers = [
|
||||||
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
||||||
|
"integration: marks tests as integration tests",
|
||||||
|
]
|
109
recalculate_project_stats.py
Normal file
109
recalculate_project_stats.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script to recalculate project statistics from imported sessions and conversations.
|
||||||
|
Run this after importing data to fix zero statistics on the projects page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from sqlalchemy import select, func
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from app.database.connection import engine, async_session_maker
|
||||||
|
from app.models.project import Project
|
||||||
|
from app.models.session import Session
|
||||||
|
from app.models.conversation import Conversation
|
||||||
|
from app.models.activity import Activity
|
||||||
|
from app.models.tool_call import ToolCall
|
||||||
|
|
||||||
|
async def recalculate_project_stats():
|
||||||
|
"""Recalculate statistics for all projects based on imported data."""
|
||||||
|
|
||||||
|
async with async_session_maker() as db:
|
||||||
|
try:
|
||||||
|
# Get all projects
|
||||||
|
result = await db.execute(select(Project))
|
||||||
|
projects = result.scalars().all()
|
||||||
|
|
||||||
|
print(f"Found {len(projects)} projects to recalculate")
|
||||||
|
|
||||||
|
for project in projects:
|
||||||
|
print(f"\nRecalculating stats for project: {project.name}")
|
||||||
|
|
||||||
|
# Get all sessions for this project
|
||||||
|
session_result = await db.execute(
|
||||||
|
select(Session).where(Session.project_id == project.id)
|
||||||
|
)
|
||||||
|
sessions = session_result.scalars().all()
|
||||||
|
session_ids = [s.id for s in sessions] if sessions else []
|
||||||
|
|
||||||
|
# Calculate session-based statistics
|
||||||
|
total_sessions = len(sessions)
|
||||||
|
total_time_minutes = sum(
|
||||||
|
s.calculated_duration_minutes or 0 for s in sessions
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find last session time
|
||||||
|
last_session_time = None
|
||||||
|
if sessions:
|
||||||
|
last_session_time = max(s.start_time for s in sessions)
|
||||||
|
|
||||||
|
# Calculate activity-based statistics
|
||||||
|
files_modified_count = 0
|
||||||
|
lines_changed_count = 0
|
||||||
|
tool_calls_count = 0
|
||||||
|
|
||||||
|
if session_ids:
|
||||||
|
activity_result = await db.execute(
|
||||||
|
select(Activity).where(Activity.session_id.in_(session_ids))
|
||||||
|
)
|
||||||
|
activities = activity_result.scalars().all()
|
||||||
|
|
||||||
|
# Count unique files modified
|
||||||
|
unique_files = set()
|
||||||
|
total_lines_changed = 0
|
||||||
|
|
||||||
|
for activity in activities:
|
||||||
|
if activity.file_path:
|
||||||
|
unique_files.add(activity.file_path)
|
||||||
|
if activity.total_lines_changed:
|
||||||
|
total_lines_changed += activity.total_lines_changed
|
||||||
|
|
||||||
|
files_modified_count = len(unique_files)
|
||||||
|
lines_changed_count = total_lines_changed
|
||||||
|
|
||||||
|
# Count tool calls
|
||||||
|
session_ids_str = [str(sid) for sid in session_ids]
|
||||||
|
tool_calls_result = await db.execute(
|
||||||
|
select(ToolCall).where(ToolCall.session_id.in_(session_ids_str))
|
||||||
|
)
|
||||||
|
tool_calls = tool_calls_result.scalars().all()
|
||||||
|
tool_calls_count = len(tool_calls)
|
||||||
|
|
||||||
|
# Update project statistics
|
||||||
|
project.total_sessions = total_sessions
|
||||||
|
project.total_time_minutes = total_time_minutes
|
||||||
|
project.files_modified_count = files_modified_count
|
||||||
|
project.lines_changed_count = lines_changed_count
|
||||||
|
project.tool_calls_count = tool_calls_count
|
||||||
|
if last_session_time:
|
||||||
|
project.last_session = last_session_time
|
||||||
|
|
||||||
|
print(f" Sessions: {total_sessions}")
|
||||||
|
print(f" Time: {total_time_minutes} minutes")
|
||||||
|
print(f" Files: {files_modified_count}")
|
||||||
|
print(f" Lines: {lines_changed_count}")
|
||||||
|
print(f" Tool calls: {tool_calls_count}")
|
||||||
|
print(f" Last session: {last_session_time}")
|
||||||
|
|
||||||
|
# Commit all changes
|
||||||
|
await db.commit()
|
||||||
|
print(f"\nSuccessfully recalculated statistics for {len(projects)} projects")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await db.rollback()
|
||||||
|
print(f"Error recalculating project stats: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
await engine.dispose()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(recalculate_project_stats())
|
@ -1,11 +0,0 @@
|
|||||||
fastapi==0.104.1
|
|
||||||
uvicorn[standard]==0.24.0
|
|
||||||
sqlalchemy==2.0.23
|
|
||||||
aiosqlite==0.19.0
|
|
||||||
pydantic==2.5.0
|
|
||||||
jinja2==3.1.2
|
|
||||||
python-multipart==0.0.6
|
|
||||||
python-jose[cryptography]==3.3.0
|
|
||||||
passlib[bcrypt]==1.7.4
|
|
||||||
python-dateutil==2.8.2
|
|
||||||
typing-extensions==4.8.0
|
|
Loading…
x
Reference in New Issue
Block a user