408fde440d
Photograph/screenshot a boarding pass, decode its BCBP barcode (PDF417/ Aztec/QR/DataMatrix), fetch the historical flight track from FlightAware the day after the flight, stage it for review, and write it into AirTrail via its REST API on approval. ntfy notifications carry signed Approve/Deny/Investigate actions.
107 lines
4.8 KiB
HTML
107 lines
4.8 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Flight {{ flight.id }} — Boarding Pass{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
{% if flight.track_coordinates %}
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<a href="{{ url_for('review.list_flights') }}" class="back-link">← All flights</a>
|
|
|
|
<div class="detail-header">
|
|
<h1>{{ flight.from_iata or '?' }} → {{ flight.to_iata or '?' }}
|
|
<span class="badge badge-{{ flight.status }}">{{ flight.status.replace('_', ' ') }}</span>
|
|
</h1>
|
|
<p class="muted">Flight #{{ flight.id }} · created {{ flight.created_at }}</p>
|
|
</div>
|
|
|
|
{% if flight.error_message %}
|
|
<div class="alert alert-danger">{{ flight.error_message }}</div>
|
|
{% endif %}
|
|
{% if flight.parse_warnings %}
|
|
<div class="alert alert-warning">
|
|
<strong>Parse warnings:</strong>
|
|
<ul>{% for w in flight.parse_warnings %}<li>{{ w }}</li>{% endfor %}</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="detail-grid">
|
|
<div class="card">
|
|
<h2>Details</h2>
|
|
<form method="post" action="{{ url_for('review.edit', flight_id=flight.id) }}" class="edit-form">
|
|
<label>Passenger name <input name="passenger_name" value="{{ flight.passenger_name or '' }}"></label>
|
|
<label>PNR <input name="pnr" value="{{ flight.pnr or '' }}"></label>
|
|
<div class="field-row">
|
|
<label>From (IATA) <input name="from_iata" value="{{ flight.from_iata or '' }}" maxlength="3"></label>
|
|
<label>To (IATA) <input name="to_iata" value="{{ flight.to_iata or '' }}" maxlength="3"></label>
|
|
</div>
|
|
<div class="field-row">
|
|
<label>Carrier (IATA) <input name="operating_carrier_iata" value="{{ flight.operating_carrier_iata or '' }}" maxlength="3"></label>
|
|
<label>Flight number <input name="flight_number" value="{{ flight.flight_number or '' }}"></label>
|
|
</div>
|
|
<div class="field-row">
|
|
<label>Flight date <input type="date" name="flight_date" value="{{ flight.flight_date or '' }}"></label>
|
|
<label>Seat <input name="seat" value="{{ flight.seat or '' }}"></label>
|
|
</div>
|
|
<label>Compartment <input name="compartment" value="{{ flight.compartment or '' }}" maxlength="1"></label>
|
|
<button type="submit" class="btn btn-secondary">Save changes</button>
|
|
</form>
|
|
|
|
<dl class="meta-list">
|
|
<dt>ICAO route</dt><dd>{{ flight.from_icao or '—' }} → {{ flight.to_icao or '—' }}</dd>
|
|
<dt>Carrier ICAO</dt><dd>{{ flight.operating_carrier_icao or '—' }}</dd>
|
|
<dt>Process after</dt><dd>{{ flight.process_after or '—' }}</dd>
|
|
<dt>Fetch attempts</dt><dd>{{ flight.fetch_attempts }}</dd>
|
|
{% if flight.flightaware_url %}
|
|
<dt>FlightAware</dt><dd><a href="{{ flight.flightaware_url }}" target="_blank" rel="noopener">{{ flight.flightaware_url }}</a></dd>
|
|
{% endif %}
|
|
{% if flight.track_point_count %}
|
|
<dt>Track points</dt><dd>{{ flight.track_point_count }}</dd>
|
|
{% endif %}
|
|
{% if flight.airtrail_flight_id %}
|
|
<dt>AirTrail flight</dt><dd>{{ flight.airtrail_flight_id }}</dd>
|
|
{% endif %}
|
|
</dl>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Photo</h2>
|
|
<a href="{{ url_for('uploaded_file', name=flight.image_path) }}" target="_blank" rel="noopener">
|
|
<img src="{{ url_for('uploaded_file', name=flight.image_path) }}" alt="Boarding pass photo" class="bp-photo">
|
|
</a>
|
|
{% if flight.raw_bcbp %}
|
|
<details class="raw-bcbp">
|
|
<summary>Raw BCBP data</summary>
|
|
<pre>{{ flight.raw_bcbp }}</pre>
|
|
</details>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if flight.track_coordinates %}
|
|
<div class="card map-card">
|
|
<h2>Flight track</h2>
|
|
<div id="track-map" data-track='{{ flight.track_coordinates | tojson }}'></div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="action-bar">
|
|
<form method="post" action="{{ url_for('review.requery', flight_id=flight.id) }}">
|
|
<button type="submit" class="btn btn-secondary">Re-query FlightAware</button>
|
|
</form>
|
|
<form method="post" action="{{ url_for('review.approve', flight_id=flight.id, token=approve_token) }}">
|
|
<button type="submit" class="btn btn-success" {% if flight.status not in ('pending_review', 'track_unavailable') %}disabled{% endif %}>Approve</button>
|
|
</form>
|
|
<form method="post" action="{{ url_for('review.reject', flight_id=flight.id, token=reject_token) }}">
|
|
<button type="submit" class="btn btn-danger" {% if flight.status in ('approved', 'rejected') %}disabled{% endif %}>Reject</button>
|
|
</form>
|
|
</div>
|
|
{% if flight.status not in ('pending_review', 'track_unavailable', 'approved', 'rejected') %}
|
|
<p class="muted">Approve is unavailable until FlightAware has been checked for this flight
|
|
(happens automatically the day after the flight, or click Re-query to check now).</p>
|
|
{% endif %}
|
|
{% endblock %}
|