Files
hermes-dashboard/templates/dashboard.html
hermes-dashboard c0cbd683f4 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
2026-08-17 20:00:37 +08:00

152 lines
6.4 KiB
HTML

{% extends "base.html" %}
{% block title %}Dashboard - Hermes{% endblock %}
{% block content %}
<div class="grid-2">
<div>
<div class="card">
<h3>🔧 Worker Status</h3>
<div id="worker-status">
{% if worker_status.current_task %}
<p><strong>Current Task:</strong> #{{ worker_status.current_task.id }} - {{ worker_status.current_task.title[:60] }}</p>
<p><strong>Worker PID:</strong> {{ worker_status.current_task.worker_pid or 'N/A' }}</p>
{% if worker_status.current_task.started_at %}
<p><strong>Elapsed:</strong> <span class="elapsed-time" data-started="{{ worker_status.current_task.started_at }}"></span></p>
{% endif %}
{% else %}
<p class="text-muted">No task currently running</p>
{% endif %}
</div>
</div>
<div class="card">
<h3>📜 SOUL.md Excerpt</h3>
<pre>{{ worker_status.soul_excerpt[:500] }}</pre>
</div>
</div>
<div>
<div class="card">
<h3>⚙️ Running Processes</h3>
<div id="process-list">
{% if processes %}
<table>
<thead>
<tr>
<th>PID</th>
<th>Elapsed</th>
<th>Command</th>
</tr>
</thead>
<tbody>
{% for proc in processes %}
<tr>
<td>{{ proc.pid }}</td>
<td><span class="elapsed-time" data-started="{{ proc.create_time }}"></span></td>
<td class="text-small">{{ proc.cmdline }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-muted">No opencode workers running</p>
{% endif %}
</div>
</div>
</div>
</div>
<h2>📋 Kanban Board</h2>
<div class="kanban-board" id="kanban-board">
{% for status in ['ready', 'running', 'done', 'blocked', 'failed'] %}
<div class="kanban-column">
<h3>{{ status|upper }} ({{ board.get(status, [])|length }})</h3>
{% for task in board.get(status, []) %}
<a href="/tasks/{{ task.id }}" style="text-decoration: none;">
<div class="kanban-card">
<div class="kanban-card-title">#{{ task.id }} {{ task.title[:60] }}</div>
<div class="kanban-card-meta">
{% if task.status == 'running' and task.started_at %}
<span class="elapsed-time" data-started="{{ task.started_at }}"></span>
{% elif task.status == 'done' and task.completed_at %}
{{ task.completed_at|rel }}
{% endif %}
{% if task.assignee %}
<br>{{ task.assignee }}
{% endif %}
</div>
</div>
</a>
{% endfor %}
</div>
{% endfor %}
</div>
<h2 class="mt-20">📤 派活 Dispatch</h2>
<div class="card">
<form id="dispatch-form">
<div style="margin-bottom:10px;">
<label for="dispatch-template">Template:</label>
<select id="dispatch-template" style="width:100%;padding:6px;background:#1a1a1a;color:#e0e0e0;border:1px solid #3a3a3a;border-radius:3px;">
<option value="">-- choose a template (optional) --</option>
</select>
</div>
<div style="margin-bottom:10px;">
<label for="dispatch-title">Title (required):</label>
<input type="text" id="dispatch-title" name="title" required maxlength="200"
style="width:100%;padding:6px;background:#1a1a1a;color:#e0e0e0;border:1px solid #3a3a3a;border-radius:3px;"
placeholder="e.g. Fix CSRF in blog-app">
</div>
<div style="margin-bottom:10px;">
<label for="dispatch-body">Body (markdown):</label>
<textarea id="dispatch-body" name="body" rows="6"
style="width:100%;padding:6px;background:#1a1a1a;color:#e0e0e0;border:1px solid #3a3a3a;border-radius:3px;font-family:inherit;"
placeholder="## 背景&#10;...&#10;## 任务&#10;...&#10;## 验收&#10;- [ ] ..."></textarea>
</div>
<div class="grid-2" style="margin-bottom:10px;">
<div>
<label for="dispatch-assignee">Assignee:</label>
<input type="text" id="dispatch-assignee" name="assignee" value="omo-pm" maxlength="64"
style="width:100%;padding:6px;background:#1a1a1a;color:#e0e0e0;border:1px solid #3a3a3a;border-radius:3px;">
</div>
<div>
<label for="dispatch-max-runtime">Max runtime (seconds, 60-7200):</label>
<input type="number" id="dispatch-max-runtime" name="max_runtime" value="1800" min="60" max="7200"
style="width:100%;padding:6px;background:#1a1a1a;color:#e0e0e0;border:1px solid #3a3a3a;border-radius:3px;">
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">🚀 Dispatch</button>
<span id="dispatch-result" class="text-small" style="margin-left:10px;"></span>
</div>
</form>
</div>
<h2 class="mt-20">📦 Project Overview</h2>
<div class="card">
<table>
<thead>
<tr>
<th>Name</th>
<th>Branch</th>
<th>Last Commit</th>
<th>Time</th>
<th>Dirty</th>
</tr>
</thead>
<tbody>
{% for proj in projects %}
<tr>
<td><a href="/projects/{{ proj.name }}">{{ proj.name }}</a></td>
<td>{{ proj.branch or 'N/A' }}</td>
<td class="text-small">{{ proj.commit_short }} {{ proj.commit_msg[:40] }}</td>
<td>{% if proj.commit_time %}{{ proj.commit_time|int|rel }}{% else %}N/A{% endif %}</td>
<td>{{ proj.dirty_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}