feat(ui): status page — progress bar, live preview, result cards, polling
This commit is contained in:
1 parent
dca6f6b335
commit
1c54bb1a11
4 files changed
+540
-56
No files matched your search
+92
-51
@@ -1,63 +1,104 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Job {{ job.job_id }} - Feedmill Recounter{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Job: {{ job.job_id }}</h2>
|
||||
|
||||
<div class="job-info">
|
||||
<p><strong>Status:</strong> <span class="status-{{ job.status.name|lower }}">{{ job.status.name }}</span></p>
|
||||
<p><strong>Video:</strong> {{ job.video_path }}</p>
|
||||
<p><strong>Progress:</strong> {{ "%.0f"|format(job.progress * 100) }}%</p>
|
||||
<div class="status-page">
|
||||
<div class="status-header">
|
||||
<div class="status-header-top">
|
||||
<span class="status-badge badge badge-{{ 'info' if job.status.name == 'RUNNING' else 'warning' if job.status.name == 'PENDING' else 'success' if job.status.name == 'COMPLETED' else 'error' if job.status.name == 'FAILED' else 'neutral' }}" id="status-badge">
|
||||
{{ job.status.name }}
|
||||
</span>
|
||||
<span class="status-time" id="created-time">{{ job.created_at | int }}</span>
|
||||
</div>
|
||||
<h2 class="job-id" id="job-title">Job {{ job.job_id[:8] }}...</h2>
|
||||
</div>
|
||||
|
||||
<div class="progress-section card" id="progress-section">
|
||||
<div class="progress-header">
|
||||
<span class="progress-label">Processing Progress</span>
|
||||
<span class="progress-pct" id="progress-pct">{{ "%.0f"|format(job.progress * 100) }}%</span>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="progress-fill" style="width: {{ "%.0f"|format(job.progress * 100) }}%"></div>
|
||||
</div>
|
||||
{% if job.current_model %}
|
||||
<p><strong>Current Model:</strong> {{ job.current_model }}</p>
|
||||
{% endif %}
|
||||
{% if job.error %}
|
||||
<p class="error"><strong>Error:</strong> {{ job.error }}</p>
|
||||
<div class="current-model" id="current-model">
|
||||
<span class="model-label">Current model:</span>
|
||||
<span class="model-name" id="model-name">{{ job.current_model }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="live-preview-section card" id="live-preview-section" style="display: {{ 'block' if job.status.name in ['RUNNING', 'PENDING'] else 'none' }}">
|
||||
<h3 class="section-title">Live Preview</h3>
|
||||
<div class="live-preview-wrap">
|
||||
<img id="live-preview-img" class="live-preview-img" alt="Annotated frame from processing" />
|
||||
<div class="preview-placeholder" id="preview-placeholder">Waiting for first frame...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-thumb-section card" id="video-thumb-section">
|
||||
<h3 class="section-title">Source Video</h3>
|
||||
<div class="video-thumb-wrap">
|
||||
<video id="video-thumb" class="video-thumb" muted preload="metadata"></video>
|
||||
<div class="video-fallback" id="video-fallback">
|
||||
<span class="video-fallback-icon">🎬</span>
|
||||
<span>Video preview unavailable</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-section" id="results-section" style="display: {{ 'block' if job.results else 'none' }}">
|
||||
<h3 class="section-title">Results</h3>
|
||||
<div class="results-grid" id="results-grid">
|
||||
{% for r in job.results %}
|
||||
<div class="result-card card">
|
||||
<div class="result-model">{{ r.model_name }}</div>
|
||||
<div class="result-stats">
|
||||
<div class="stat">
|
||||
<span class="stat-value stat-loading">{{ r.loading_count }}</span>
|
||||
<span class="stat-label">Loading</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-value stat-unloading">{{ r.unloading_count }}</span>
|
||||
<span class="stat-label">Unloading</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-value stat-net">{{ r.net_count }}</span>
|
||||
<span class="stat-label">Net</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-meta">
|
||||
<span class="meta-item"><strong>Batches:</strong> {{ r.batch_count }}</span>
|
||||
<span class="meta-item"><strong>Frames:</strong> {{ r.frame_count }}</span>
|
||||
<span class="meta-item"><strong>Duration:</strong> {{ "%.1f"|format(r.duration_seconds) }}s</span>
|
||||
</div>
|
||||
{% if r.output_path %}
|
||||
<a href="/download/{{ job.job_id }}/{{ r.output_path | basename }}" class="btn btn-primary result-download">Download</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-alert card" id="error-alert" style="display: {{ 'block' if job.error else 'none' }}">
|
||||
<span class="error-icon">⚠</span>
|
||||
<div class="error-content">
|
||||
<strong>Job Failed</strong>
|
||||
<p id="error-message">{{ job.error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if job.results %}
|
||||
<h3>Results</h3>
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Loading</th>
|
||||
<th>Unloading</th>
|
||||
<th>Net</th>
|
||||
<th>Batches</th>
|
||||
<th>Frames</th>
|
||||
<th>Duration</th>
|
||||
<th>Output</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in job.results %}
|
||||
<tr>
|
||||
<td>{{ r.model_name }}</td>
|
||||
<td>{{ r.loading_count }}</td>
|
||||
<td>{{ r.unloading_count }}</td>
|
||||
<td>{{ r.net_count }}</td>
|
||||
<td>{{ r.batch_count }}</td>
|
||||
<td>{{ r.frame_count }}</td>
|
||||
<td>{{ "%.1f"|format(r.duration_seconds) }}s</td>
|
||||
<td>
|
||||
{% if r.output_path %}
|
||||
<a href="/download/{{ job.job_id }}/{{ r.output_path | basename }}"
|
||||
class="download-btn">Download</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% if job.status.name == "RUNNING" or job.status.name == "PENDING" %}
|
||||
<div class="auto-refresh">
|
||||
<p>Status will auto-refresh...</p>
|
||||
</div>
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
||||
<script>
|
||||
setTimeout(function() { location.reload(); }, 2000);
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (typeof initStatusPage === 'function') {
|
||||
initStatusPage('{{ job.job_id }}', '{{ job.status.name }}');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user