Initial import: Music_Server, MusicFree, catalog-sync
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
{% extends "ops/base.html" %}
|
||||
{% block content %}
|
||||
{% set done_statuses = ("completed", "completed_with_errors", "failed", "canceled") %}
|
||||
{% macro render_task_tree_node(row) -%}
|
||||
{% set row_status = row.status or "" %}
|
||||
{% set toggle_command = "resume" if row_status in ("paused", "pause_requested") else "pause" if row_status in ("queued", "running") else "" %}
|
||||
{% set can_cancel = row_status in ("queued", "running", "paused", "pause_requested") %}
|
||||
<section class="task-tree-node task-tree-node-task" data-task-node="{{ row.id }}">
|
||||
<div class="task-tree-row">
|
||||
<button type="button" class="tree-toggle" data-task-toggle="{{ row.id }}" aria-expanded="false" aria-label="Expand task {{ row.id }}">+</button>
|
||||
<div class="task-tree-main">
|
||||
<div class="task-tree-title-line">
|
||||
<strong data-task-name>{{ row.display_name }}</strong>
|
||||
<span class="muted task-tree-meta-inline" data-task-meta-inline>#{{ row.id }} / {{ row.job_type }} / {{ row.scope_summary }} / {{ row.queue_label or row.lane_type or "-" }} / workers {{ row.active_worker_count }}</span>
|
||||
<span class="status-tag status-{{ row.status }}" data-task-status>{{ row.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-tree-progress" data-task-progress>
|
||||
<div class="progress-meta">
|
||||
<span>{{ row.primary_progress_text or "-" }}</span>
|
||||
<strong>{{ row.primary_progress_percent or 0 }}%</strong>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: {{ row.primary_progress_percent or 0 }}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-tree-actions">
|
||||
<div class="button-grid">
|
||||
{% if toggle_command %}
|
||||
<button
|
||||
type="button"
|
||||
data-task-command-toggle="{{ row.id }}"
|
||||
data-task-command-type="{{ toggle_command }}"
|
||||
>
|
||||
{% if toggle_command == "resume" %}>{% else %}||{% endif %}
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="muted">-</span>
|
||||
{% endif %}
|
||||
{% if can_cancel %}
|
||||
<button type="button" class="secondary" data-task-command-cancel="{{ row.id }}">x</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-tree-children" data-task-children="{{ row.id }}" hidden>
|
||||
<p class="muted">Expand to load playlists...</p>
|
||||
</div>
|
||||
</section>
|
||||
{%- endmacro %}
|
||||
|
||||
<h1>Task Center</h1>
|
||||
|
||||
<div class="card">
|
||||
<div data-live-status>Live snapshot: waiting...</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h2>Summary</h2>
|
||||
<table>
|
||||
<tr><th>Total Jobs</th><td data-summary-field="total_jobs">{{ summary.total_jobs }}</td></tr>
|
||||
<tr><th>Queued</th><td data-summary-field="queued_jobs">{{ summary.queued_jobs }}</td></tr>
|
||||
<tr><th>Queued Download Jobs</th><td data-summary-field="queued_download_jobs">{{ summary.queued_download_jobs }}</td></tr>
|
||||
<tr><th>Running</th><td data-summary-field="running_jobs">{{ summary.running_jobs }}</td></tr>
|
||||
<tr><th>Paused</th><td data-summary-field="paused_jobs">{{ summary.paused_jobs }}</td></tr>
|
||||
<tr><th>Failed / Errors</th><td data-summary-field="failed_jobs">{{ summary.failed_jobs }}</td></tr>
|
||||
<tr><th>Downloaded Songs</th><td data-download-field="downloaded_songs">{{ download_stats.downloaded_songs }}</td></tr>
|
||||
<tr><th>Running Songs</th><td data-download-field="running_song_items">{{ download_stats.running_song_items }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Quick Actions</h2>
|
||||
<div class="button-grid">
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<input type="hidden" name="job_type" value="catalog_sync" />
|
||||
<input type="hidden" name="requested_by" value="ops-console" />
|
||||
<input type="hidden" name="sources" value="{{ default_sources }}" />
|
||||
<input type="hidden" name="download_sources" value="{{ default_download_sources }}" />
|
||||
<button type="submit">Full Pipeline</button>
|
||||
</form>
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<input type="hidden" name="job_type" value="collect_only" />
|
||||
<input type="hidden" name="requested_by" value="ops-console" />
|
||||
<input type="hidden" name="sources" value="{{ default_sources }}" />
|
||||
<button type="submit">Collect</button>
|
||||
</form>
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<input type="hidden" name="job_type" value="sync_only" />
|
||||
<input type="hidden" name="requested_by" value="ops-console" />
|
||||
<input type="hidden" name="sources" value="{{ default_sources }}" />
|
||||
<button type="submit">Sync</button>
|
||||
</form>
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<input type="hidden" name="job_type" value="download_only" />
|
||||
<input type="hidden" name="requested_by" value="ops-console" />
|
||||
<input type="hidden" name="download_sources" value="{{ default_download_sources }}" />
|
||||
<button type="submit">Download</button>
|
||||
</form>
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<input type="hidden" name="job_type" value="upload_only" />
|
||||
<input type="hidden" name="requested_by" value="ops-console" />
|
||||
<input type="hidden" name="download_sources" value="{{ default_download_sources }}" />
|
||||
<button type="submit">Upload</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Create Job</h2>
|
||||
<form action="/api/jobs" method="post" data-json-form data-success="reload">
|
||||
<label>
|
||||
Job Type
|
||||
<select name="job_type">
|
||||
{% for value, label in job_type_options %}
|
||||
<option value="{{ value }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Requested By
|
||||
<input type="text" name="requested_by" value="ops-console" />
|
||||
</label>
|
||||
<label>
|
||||
Collect Sources
|
||||
<input type="text" name="sources" value="{{ default_sources }}" />
|
||||
</label>
|
||||
<label>
|
||||
Download Sources
|
||||
<input type="text" name="download_sources" value="{{ default_download_sources }}" />
|
||||
</label>
|
||||
<button type="submit">Create Job</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Playlist Coverage</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>Pool Kind</th>
|
||||
<th>Pool Name</th>
|
||||
<th>Playlists</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-playlist-sources-body>
|
||||
{% for row in playlist_sources %}
|
||||
<tr>
|
||||
<td>{{ row.platform }}</td>
|
||||
<td>{{ row.pool_kind }}</td>
|
||||
<td>{{ row.pool_name }}</td>
|
||||
<td>{{ row.playlist_count }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="4">No playlist sources collected yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="card"
|
||||
data-maintenance-panel="local-duplicates"
|
||||
data-scan-api="{{ maintenance_local_duplicates_scan_api }}"
|
||||
data-dedupe-api="{{ maintenance_local_duplicates_dedupe_api }}"
|
||||
>
|
||||
<h2>Maintenance</h2>
|
||||
<div class="button-grid">
|
||||
<button type="button" data-maintenance-action="scan">Scan Duplicate Local Copies</button>
|
||||
<button type="button" class="secondary" data-maintenance-action="dedupe">Run Local Dedupe</button>
|
||||
</div>
|
||||
<p class="muted" data-maintenance-status>No local duplicate scan has been run yet.</p>
|
||||
<div data-maintenance-result>
|
||||
<p class="muted">Scan first to inspect duplicate local file copies before dedupe.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="task-tree-panel-head">
|
||||
<h2>Task Center</h2>
|
||||
<span class="muted" data-task-center-transfer>Down {{ transfer_stats.download_speed_text }} | Up {{ transfer_stats.upload_speed_text }}</span>
|
||||
</div>
|
||||
<div class="task-tree-columns">
|
||||
<section class="task-tree-panel">
|
||||
<div class="task-tree-panel-head">
|
||||
<h3>Doing</h3>
|
||||
<span class="muted">Task -> Playlist -> Song</span>
|
||||
</div>
|
||||
<div class="task-tree" data-task-tree-root="doing">
|
||||
{% for row in doing_task_rows %}
|
||||
{{ render_task_tree_node(row) }}
|
||||
{% else %}
|
||||
<p class="muted" data-task-tree-empty>No active tasks.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
<section class="task-tree-panel">
|
||||
<div class="task-tree-panel-head">
|
||||
<h3>Recent Done</h3>
|
||||
<span class="muted">Task -> Playlist</span>
|
||||
</div>
|
||||
<div class="task-tree" data-task-tree-root="done">
|
||||
{% for row in done_task_rows %}
|
||||
{{ render_task_tree_node(row) }}
|
||||
{% else %}
|
||||
<p class="muted" data-task-tree-empty>No recently finished tasks.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h2>Active Workers</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Worker</th>
|
||||
<th>Status</th>
|
||||
<th>Stage</th>
|
||||
<th>Current Item</th>
|
||||
<th>Progress</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-workers-body>
|
||||
{% for worker in workers %}
|
||||
<tr>
|
||||
<td>{{ worker.worker_name }}</td>
|
||||
<td>{{ worker.status }}</td>
|
||||
<td>{{ worker.stage_type or "-" }}</td>
|
||||
<td>{{ worker.display_text or "-" }}</td>
|
||||
<td>{{ worker.last_progress_text or "-" }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5">No active workers.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Running Items</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job</th>
|
||||
<th>Worker</th>
|
||||
<th>Stage</th>
|
||||
<th>Item</th>
|
||||
<th>Started</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-running-items-body>
|
||||
{% for item in running_items %}
|
||||
<tr>
|
||||
<td><a href="/jobs/{{ item.job_run_id }}">{{ item.job_run_id }}</a></td>
|
||||
<td>{{ item.worker_name or "-" }}</td>
|
||||
<td>{{ item.stage_type }}</td>
|
||||
<td>{{ item.display_name }}</td>
|
||||
<td>{{ item.started_at or "-" }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5">No running items.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user