feat: Flask web UI on port 9000 with upload, job status, API endpoints
This commit is contained in:
1 parent
f38c9708f6
commit
eb77e5be8a
6 files changed
+373
No files matched your search
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Feedmill Recounter{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Feedmill Recounter</h1>
|
||||
<nav>
|
||||
<a href="/">Upload</a>
|
||||
<a href="/jobs">Jobs</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Upload - Feedmill Recounter{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Upload Video & Select Models</h2>
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label for="video">Video File:</label>
|
||||
<input type="file" name="video" id="video" accept="video/*" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Models to Run:</label>
|
||||
{% if models %}
|
||||
<div class="model-list">
|
||||
{% for model in models %}
|
||||
<div class="model-item">
|
||||
<input type="checkbox" name="models" value="{{ model.filename }}" id="m-{{ loop.index }}">
|
||||
<label for="m-{{ loop.index }}">
|
||||
<strong>{{ model.filename }}</strong>
|
||||
{% if model.known_classes %}
|
||||
<span class="classes">[{{ model.known_classes | join(', ') }}]</span>
|
||||
{% else %}
|
||||
<span class="classes unknown">[no class info]</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
<div class="filter-group">
|
||||
<label>Class filter:</label>
|
||||
<select name="filter_{{ model.filename }}">
|
||||
<option value="default">Use model defaults</option>
|
||||
<option value="sack">sack only</option>
|
||||
<option value="box">box only</option>
|
||||
<option value="sack,box">sack + box</option>
|
||||
<option value="all">all classes</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="warning">No models found in {{ models_dir }}. Place model files in the models/ directory.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" {% if not models %}disabled{% endif %}>Start Processing</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Jobs - Feedmill Recounter{% endblock %}
|
||||
{% block content %}
|
||||
<h2>All Jobs</h2>
|
||||
{% if jobs %}
|
||||
<table class="results-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job ID</th>
|
||||
<th>Status</th>
|
||||
<th>Progress</th>
|
||||
<th>Models</th>
|
||||
<th>Created</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for job in jobs %}
|
||||
<tr>
|
||||
<td><a href="/status/{{ job.job_id }}">{{ job.job_id }}</a></td>
|
||||
<td class="status-{{ job.status|lower }}">{{ job.status.name }}</td>
|
||||
<td>{{ "%.0f"|format(job.progress * 100) }}%</td>
|
||||
<td>{{ job.model_configs|length }} model(s)</td>
|
||||
<td>{{ "%.1f"|format(job.created_at) }}</td>
|
||||
<td>
|
||||
<a href="/status/{{ job.job_id }}">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No jobs yet. <a href="/">Upload a video</a></p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,63 @@
|
||||
{% 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|lower }}">{{ job.status }}</span></p>
|
||||
<p><strong>Video:</strong> {{ job.video_path }}</p>
|
||||
<p><strong>Progress:</strong> {{ "%.0f"|format(job.progress * 100) }}%</p>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
|
||||
{% if job.status == "RUNNING" or job.status == "PENDING" %}
|
||||
<div class="auto-refresh">
|
||||
<p>Status will auto-refresh...</p>
|
||||
</div>
|
||||
<script>
|
||||
setTimeout(function() { location.reload(); }, 2000);
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user