Adds the full Django application layer on top of sportstime_parser: - core: Sport, Team, Stadium, Game models with aliases and league structure - scraper: orchestration engine, adapter, job management, Celery tasks - cloudkit: CloudKit sync client, sync state tracking, sync jobs - dashboard: staff dashboard for monitoring scrapers, sync, review queue - notifications: email reports for scrape/sync results - Docker setup for deployment (Dockerfile, docker-compose, entrypoint) Game exports now use game_datetime_utc (ISO 8601 UTC) instead of venue-local date+time strings, matching the canonical format used by the iOS app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
4.0 KiB
HTML
101 lines
4.0 KiB
HTML
{% extends "dashboard/base.html" %}
|
|
|
|
{% block dashboard_content %}
|
|
<div class="stat-card">
|
|
<h3>Scraper Status</h3>
|
|
<div class="stat-grid">
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ running_jobs }}</div>
|
|
<div class="stat-label">Running</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ pending_jobs }}</div>
|
|
<div class="stat-label">Pending</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-value">{{ configs.count }}</div>
|
|
<div class="stat-label">Configurations</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Scraper Configurations</h3>
|
|
<div class="table-responsive">
|
|
<table class="dashboard-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Sport</th>
|
|
<th>Season</th>
|
|
<th>Enabled</th>
|
|
<th>Last Run</th>
|
|
<th>Status</th>
|
|
<th>Games</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for config in configs %}
|
|
<tr>
|
|
<td><strong>{{ config.sport.short_name }}</strong></td>
|
|
<td>{{ config.sport.get_season_display }}</td>
|
|
<td>{% if config.is_enabled %}<span style="color: green;">✓</span>{% else %}<span style="color: #999;">✗</span>{% endif %}</td>
|
|
<td>{% if config.last_run %}{{ config.last_run|timesince }} ago{% else %}-{% endif %}</td>
|
|
<td>
|
|
{% if config.last_run_status %}
|
|
<span class="status-badge status-{{ config.last_run_status }}">{{ config.last_run_status|upper }}</span>
|
|
{% else %}-{% endif %}
|
|
</td>
|
|
<td>{{ config.last_run_games }}</td>
|
|
<td>
|
|
<form method="post" action="{% url 'dashboard:run_scraper' config.sport.code config.season %}" style="display: inline;">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn-action" {% if not config.is_enabled %}disabled{% endif %}>Run Now</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<h3>Recent Jobs</h3>
|
|
<div class="table-responsive">
|
|
<table class="dashboard-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Sport</th>
|
|
<th>Status</th>
|
|
<th>Trigger</th>
|
|
<th>Started</th>
|
|
<th>Duration</th>
|
|
<th>Games</th>
|
|
<th>Reviews</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for job in recent_jobs %}
|
|
<tr>
|
|
<td><a href="{% url 'admin:scraper_scrapejob_change' job.id %}">{{ job.id }}</a></td>
|
|
<td>{{ job.config.sport.short_name }} {{ job.config.season }}</td>
|
|
<td><span class="status-badge status-{{ job.status }}">{{ job.status|upper }}</span></td>
|
|
<td>{{ job.triggered_by }}</td>
|
|
<td>{% if job.started_at %}{{ job.started_at|timesince }} ago{% else %}-{% endif %}</td>
|
|
<td>{{ job.duration_display }}</td>
|
|
<td>
|
|
{% if job.games_found %}
|
|
{{ job.games_found }} ({{ job.games_new }} new, {{ job.games_updated }} upd)
|
|
{% else %}-{% endif %}
|
|
</td>
|
|
<td>{% if job.review_items_created %}{{ job.review_items_created }}{% else %}-{% endif %}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|