feat: project overview + timeline + docs center
- services/task_project_linker.py: task<->project mapping (title keywords + workspace_path) - routes/pages.py: /timeline, /docs, /docs/<path>, enhanced /projects/<name> and /tasks/<id> - templates: timeline.html, docs.html, doc_view.html (new), project_detail.html + task_detail.html (overhauled) - config.py: DOCS_DIRS + ProjectConfig color - base.html: Timeline + Docs nav links
This commit is contained in:
@@ -342,6 +342,8 @@
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/projects">Projects</a>
|
||||
<a href="/tasks">Tasks</a>
|
||||
<a href="/timeline">Timeline</a>
|
||||
<a href="/docs">Docs</a>
|
||||
</nav>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-20">📦 Project Status</h2>
|
||||
<h2 class="mt-20">📦 Project Overview</h2>
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
81
templates/doc_view.html
Normal file
81
templates/doc_view.html
Normal file
@@ -0,0 +1,81 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }} - Docs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="text-small text-muted">
|
||||
<a href="/docs">Docs</a> > {{ rel_path }}
|
||||
</p>
|
||||
|
||||
<h2>📄 {{ title }}</h2>
|
||||
|
||||
<div class="card">
|
||||
{% if rendered_html %}
|
||||
<div class="markdown-body">
|
||||
{{ rendered_html|safe }}
|
||||
</div>
|
||||
{% else %}
|
||||
<pre>{{ raw }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="mt-20">
|
||||
<a href="/docs" class="btn">← Back to Docs</a>
|
||||
</p>
|
||||
|
||||
<style>
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4 {
|
||||
color: #fff;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #3a3a3a;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.markdown-body h1 { font-size: 22px; }
|
||||
.markdown-body h2 { font-size: 18px; }
|
||||
.markdown-body h3 { font-size: 16px; }
|
||||
.markdown-body p { margin: 10px 0; }
|
||||
.markdown-body code {
|
||||
background: #1a1a1a;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
color: #ff9e4a;
|
||||
}
|
||||
.markdown-body pre {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #3a3a3a;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.markdown-body pre code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.markdown-body ul, .markdown-body ol {
|
||||
margin-left: 25px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.markdown-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.markdown-body th, .markdown-body td {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #3a3a3a;
|
||||
}
|
||||
.markdown-body th { background: #333; }
|
||||
.markdown-body blockquote {
|
||||
border-left: 3px solid #4a9eff;
|
||||
padding-left: 15px;
|
||||
margin: 10px 0;
|
||||
color: #aaa;
|
||||
}
|
||||
.markdown-body a { color: #4a9eff; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
44
templates/docs.html
Normal file
44
templates/docs.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Docs - Hermes{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>📚 Documentation Center</h2>
|
||||
|
||||
<div class="card">
|
||||
<p class="text-muted">{{ docs|length }} documents across all docs directories</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
{% if docs %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Path</th>
|
||||
<th>Size</th>
|
||||
<th>Modified</th>
|
||||
<th>Snippet</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in docs %}
|
||||
<tr>
|
||||
<td><a href="/docs/{{ d.rel_path }}">{{ d.title }}</a></td>
|
||||
<td class="text-small text-muted">{{ d.rel_path }}</td>
|
||||
<td class="text-small">{{ d.size }} B</td>
|
||||
<td class="text-small">{{ d.mtime|int|dt }}</td>
|
||||
<td class="text-small text-muted">{{ d.snippet[:80] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No documents found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="mt-20">
|
||||
<a href="/" class="btn">← Back to Dashboard</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -54,14 +54,92 @@
|
||||
</div>
|
||||
|
||||
<div class="card mt-20">
|
||||
<h3>Recent Commits (last 10)</h3>
|
||||
{% if project.recent_commits %}
|
||||
<pre>{% for commit in project.recent_commits %}{{ commit }}
|
||||
{% endfor %}</pre>
|
||||
<h3>📋 Task Timeline ({{ tasks|length }} tasks)</h3>
|
||||
{% if tasks %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Duration</th>
|
||||
<th>Outcome</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in tasks %}
|
||||
<tr>
|
||||
<td class="text-small"><a href="/tasks/{{ t.task_id }}">{{ t.task_id }}</a></td>
|
||||
<td>{{ t.title[:80] }}</td>
|
||||
<td><span class="status-badge status-{{ t.status }}">{{ t.status }}</span></td>
|
||||
<td class="text-small">{% if t.created_at %}{{ t.created_at|int|dt }}{% endif %}</td>
|
||||
<td class="text-small">
|
||||
{% if t.duration_s %}
|
||||
{% if t.duration_s < 60 %}{{ t.duration_s }}s
|
||||
{% elif t.duration_s < 3600 %}{{ (t.duration_s / 60)|round(1) }}m
|
||||
{% else %}{{ (t.duration_s / 3600)|round(1) }}h{% endif %}
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="text-small text-muted">{{ t.outcome_summary[:80] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No tasks linked to this project</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card mt-20">
|
||||
<h3>🔀 Recent Commits ({{ commits|length }})</h3>
|
||||
{% if commits %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hash</th>
|
||||
<th>Author</th>
|
||||
<th>Time</th>
|
||||
<th>Subject</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in commits %}
|
||||
<tr>
|
||||
<td class="text-small"><a href="{{ c.gitea_url }}" target="_blank">{{ c.short }}</a></td>
|
||||
<td class="text-small">{{ c.author }}</td>
|
||||
<td class="text-small">{{ c.ts|int|dt }}</td>
|
||||
<td>{{ c.subject }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No commits found</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if task_files %}
|
||||
<div class="card mt-20">
|
||||
<h3>📁 Files Changed (per task time window)</h3>
|
||||
{% for task_id, commit_files in task_files.items() %}
|
||||
<div style="margin-bottom:15px;">
|
||||
<h4 style="margin-bottom:5px;">
|
||||
<a href="/tasks/{{ task_id }}">{{ task_id }}</a>
|
||||
</h4>
|
||||
{% for chash, files in commit_files.items() %}
|
||||
<div class="text-small" style="margin-left:15px;">
|
||||
<strong>{{ chash }}</strong>: {{ files|length }} file(s)
|
||||
<ul style="margin-left:20px;">
|
||||
{% for f in files[:10] %}<li>{{ f }}</li>{% endfor %}
|
||||
{% if files|length > 10 %}<li class="text-muted">... {{ files|length - 10 }} more</li>{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<p class="mt-20">
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
{% set task = data.task %}
|
||||
<h2>📋 Task #{{ task.id }}: {{ task.title }}</h2>
|
||||
|
||||
{% if project_name %}
|
||||
<div class="card mb-20">
|
||||
<p><strong>📦 Project:</strong> <a href="/projects/{{ project_name }}">{{ project_name }}</a></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<div class="card">
|
||||
@@ -44,6 +50,41 @@
|
||||
<pre>{{ task.result }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if commits %}
|
||||
<div class="card">
|
||||
<h3>🔀 Commits During Task ({{ commits|length }})</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Hash</th><th>Time</th><th>Subject</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in commits %}
|
||||
<tr>
|
||||
<td class="text-small"><a href="{{ c.gitea_url }}" target="_blank">{{ c.short }}</a></td>
|
||||
<td class="text-small">{{ c.ts|int|dt }}</td>
|
||||
<td>{{ c.subject }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if files_changed %}
|
||||
<div class="card">
|
||||
<h3>📁 Files Changed</h3>
|
||||
{% for chash, files in files_changed.items() %}
|
||||
<div class="text-small" style="margin-bottom:10px;">
|
||||
<strong>{{ chash }}</strong>:
|
||||
<ul style="margin-left:20px;">
|
||||
{% for f in files[:15] %}<li>{{ f }}</li>{% endfor %}
|
||||
{% if files|length > 15 %}<li class="text-muted">... {{ files|length - 15 }} more</li>{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
95
templates/timeline.html
Normal file
95
templates/timeline.html
Normal file
@@ -0,0 +1,95 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Timeline - Hermes{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>🕐 Full Task Timeline</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Summary</h3>
|
||||
<p>
|
||||
<strong>Total:</strong> {{ stats.total }} |
|
||||
<strong>Done:</strong> <span class="status-badge status-done">{{ stats.done }}</span> |
|
||||
<strong>Blocked:</strong> <span class="status-badge status-blocked">{{ stats.blocked }}</span> |
|
||||
<strong>Running:</strong> <span class="status-badge status-running">{{ stats.running }}</span> |
|
||||
<strong>Ready:</strong> <span class="status-badge status-ready">{{ stats.ready }}</span> |
|
||||
<strong>Failed:</strong> <span class="status-badge status-failed">{{ stats.failed }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% for pname, tasks in by_project.items() %}
|
||||
{% if tasks %}
|
||||
<div class="card mt-20">
|
||||
<h3>
|
||||
{% if pname != 'unassigned' %}
|
||||
<a href="/projects/{{ pname }}" style="color: {{ proj_colors.get(pname, '#4a9eff') }};">📦 {{ pname }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">📦 {{ pname }}</span>
|
||||
{% endif %}
|
||||
<span class="text-small text-muted">({{ tasks|length }} tasks)</span>
|
||||
</h3>
|
||||
|
||||
{% set ns = namespace(min_ts=99999999999, max_ts=0) %}
|
||||
{% for t in tasks %}
|
||||
{% if t.created_at and t.created_at < ns.min_ts %}{% set ns.min_ts = t.created_at %}{% endif %}
|
||||
{% if t.completed_at and t.completed_at > ns.max_ts %}{% set ns.max_ts = t.completed_at %}{% endif %}
|
||||
{% if t.created_at and not t.completed_at and t.created_at > ns.max_ts %}{% set ns.max_ts = t.created_at %}{% endif %}
|
||||
{% endfor %}
|
||||
{% set span = (ns.max_ts - ns.min_ts) if ns.max_ts > ns.min_ts else 1 %}
|
||||
|
||||
{% set status_color = {'done': '#4aff4a', 'blocked': '#ff4a4a', 'running': '#4a9eff', 'ready': '#aaaaaa', 'failed': '#ff9e4a'} %}
|
||||
<div style="background:#1a1a1a;padding:10px;border-radius:3px;margin-bottom:15px;">
|
||||
{% for t in tasks[:20] %}
|
||||
{% if t.created_at %}
|
||||
{% set left = ((t.created_at - ns.min_ts) / span * 100)|round(2) %}
|
||||
{% set width = (((t.completed_at or t.created_at) - t.created_at) / span * 100)|round(2) %}
|
||||
{% if width < 0.5 %}{% set width = 0.5 %}{% endif %}
|
||||
{% set bar_color = status_color.get(t.status, '#888') %}
|
||||
<div style="position:relative;height:18px;margin-bottom:2px;">
|
||||
<a href="/tasks/{{ t.task_id }}" style="display:block;position:absolute;left:{{ left }}%;width:{{ width }}%;height:100%;background:{{ bar_color }};opacity:0.75;border-radius:2px;text-decoration:none;" title="[{{ t.status }}] {{ t.title[:60] }}"></a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="text-small text-muted" style="margin-top:5px;">
|
||||
{% if ns.min_ts < 99999999999 %}{{ ns.min_ts|int|dt }} → {{ ns.max_ts|int|dt }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Duration</th>
|
||||
<th>Outcome</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in tasks %}
|
||||
<tr>
|
||||
<td class="text-small"><a href="/tasks/{{ t.task_id }}">{{ t.task_id }}</a></td>
|
||||
<td>{{ t.title[:80] }}</td>
|
||||
<td><span class="status-badge status-{{ t.status }}">{{ t.status }}</span></td>
|
||||
<td class="text-small">{% if t.created_at %}{{ t.created_at|int|dt }}{% endif %}</td>
|
||||
<td class="text-small">
|
||||
{% if t.duration_s %}
|
||||
{% if t.duration_s < 60 %}{{ t.duration_s }}s
|
||||
{% elif t.duration_s < 3600 %}{{ (t.duration_s / 60)|round(1) }}m
|
||||
{% else %}{{ (t.duration_s / 3600)|round(1) }}h{% endif %}
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="text-small text-muted">{{ t.outcome_summary[:80] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<p class="mt-20">
|
||||
<a href="/" class="btn">← Back to Dashboard</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user