## 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>
214 lines
7.8 KiB
HTML
214 lines
7.8 KiB
HTML
{% 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 %} |