Add run tagging (DB column + migration + route + UI)
- Added tags VARCHAR(300) column to Run model - Added startup ALTER TABLE migration in app.py - New POST /run/<id>/set_tags route saves comma-separated tags - Tag badges + collapsible edit form in run_details.html header area Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tags -->
|
||||||
|
<div class="mb-3 d-flex align-items-center gap-2 flex-wrap">
|
||||||
|
{% if run.tags %}
|
||||||
|
{% for tag in run.tags.split(',') %}
|
||||||
|
<span class="badge bg-secondary fs-6">{{ tag }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted small fst-italic">No tags</span>
|
||||||
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#tagsForm">
|
||||||
|
<i class="fas fa-tag me-1"></i>Edit Tags
|
||||||
|
</button>
|
||||||
|
<div class="collapse w-100" id="tagsForm">
|
||||||
|
<form action="{{ url_for('run.set_tags', id=run.id) }}" method="POST" class="d-flex gap-2 mt-1">
|
||||||
|
<input type="text" name="tags" class="form-control form-control-sm"
|
||||||
|
value="{{ run.tags or '' }}"
|
||||||
|
placeholder="comma-separated tags, e.g. dark-ending, v2, favourite">
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Status Bar -->
|
<!-- Status Bar -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="card shadow-sm mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@@ -116,6 +116,14 @@ with app.app_context():
|
|||||||
_log("System: Added 'last_heartbeat' column to Run table.")
|
_log("System: Added 'last_heartbeat' column to Run table.")
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
|
# Migration: Add 'tags' column if missing
|
||||||
|
try:
|
||||||
|
with db.engine.connect() as conn:
|
||||||
|
conn.execute(text("ALTER TABLE run ADD COLUMN tags VARCHAR(300)"))
|
||||||
|
conn.commit()
|
||||||
|
_log("System: Added 'tags' column to Run table.")
|
||||||
|
except: pass
|
||||||
|
|
||||||
# Reset all non-terminal runs on startup (running, queued, interrupted)
|
# Reset all non-terminal runs on startup (running, queued, interrupted)
|
||||||
# The Huey consumer restarts with the app, so any in-flight tasks are gone.
|
# The Huey consumer restarts with the app, so any in-flight tasks are gone.
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class Run(db.Model):
|
|||||||
progress = db.Column(db.Integer, default=0)
|
progress = db.Column(db.Integer, default=0)
|
||||||
last_heartbeat = db.Column(db.DateTime, nullable=True)
|
last_heartbeat = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
|
tags = db.Column(db.String(300), nullable=True)
|
||||||
|
|
||||||
logs = db.relationship('LogEntry', backref='run', lazy=True, cascade="all, delete-orphan")
|
logs = db.relationship('LogEntry', backref='run', lazy=True, cascade="all, delete-orphan")
|
||||||
|
|
||||||
def duration(self):
|
def duration(self):
|
||||||
|
|||||||
@@ -394,6 +394,22 @@ def revise_book(run_id, book_folder):
|
|||||||
return redirect(url_for('run.view_run', id=new_run.id))
|
return redirect(url_for('run.view_run', id=new_run.id))
|
||||||
|
|
||||||
|
|
||||||
|
@run_bp.route('/run/<int:id>/set_tags', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def set_tags(id):
|
||||||
|
run = db.session.get(Run, id)
|
||||||
|
if not run: return "Run not found", 404
|
||||||
|
if run.project.user_id != current_user.id: return "Unauthorized", 403
|
||||||
|
|
||||||
|
raw = request.form.get('tags', '')
|
||||||
|
tags = [t.strip() for t in raw.split(',') if t.strip()]
|
||||||
|
run.tags = ','.join(dict.fromkeys(tags))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
flash("Tags updated.")
|
||||||
|
return redirect(url_for('run.view_run', id=id))
|
||||||
|
|
||||||
|
|
||||||
@run_bp.route('/run/<int:id>/delete', methods=['POST'])
|
@run_bp.route('/run/<int:id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_run(id):
|
def delete_run(id):
|
||||||
|
|||||||
Reference in New Issue
Block a user