Files
meubility-workbench/app/templates/index.html

387 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mobility Workbench</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" crossorigin="" />
<link rel="stylesheet" href="/static/style.css?v={{ static_version }}" />
</head>
<body>
<header>
<div>
<h1>Mobility Workbench</h1>
<p>Harmonized transit, mapping data, route layer, map review, and journey tests.</p>
</div>
<div class="actions">
<button id="refreshBtn">Refresh</button>
</div>
</header>
<main>
<aside>
<div class="sidebar-content">
<details class="card sidebar-section" data-sidebar-section="stats" open>
<summary><h2>Stats</h2></summary>
<div class="sidebar-section-body">
<div id="stats" class="stats"></div>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="qa" open>
<summary><h2>QA</h2></summary>
<div class="sidebar-section-body">
<div class="qa-toolbar">
<button type="button" id="refreshQaBtn">Refresh QA</button>
</div>
<div id="qaDashboard" class="qa-dashboard muted">No QA loaded.</div>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="jobs" open>
<summary><h2>Jobs</h2></summary>
<div class="sidebar-section-body">
<div id="jobs" class="jobs muted">No jobs loaded.</div>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="harmonization" open>
<summary><h2>GTFS Harmonization</h2></summary>
<div class="sidebar-section-body">
<details class="nested-section" data-sidebar-section="add-gtfs-source">
<summary><h3>Add GTFS feed</h3></summary>
<div class="nested-section-body">
<form id="sourceForm">
<input name="catalog_entry_id" type="hidden" />
<input name="kind" type="hidden" value="gtfs" />
<label>Name <input name="name" required placeholder="DELFI / national GTFS" /></label>
<label>URL or path <input name="url" required placeholder="https://.../feed.zip or ./data/feed.zip" /></label>
<label>Country <input name="country" placeholder="DE" maxlength="8" /></label>
<label>License <input name="license" placeholder="ODbL / CC-BY / unknown" /></label>
<button type="submit">Add GTFS feed</button>
</form>
</div>
</details>
<details class="nested-section source-catalog-card" data-sidebar-section="source-catalog">
<summary><h3>Discovery catalog</h3></summary>
<div class="nested-section-body">
<div id="sourceCatalogSummary" class="muted"></div>
<div class="filter-row source-catalog-filter">
<input id="sourceCatalogSearch" placeholder="Search catalog" />
<input id="sourceCatalogCountry" placeholder="Country" />
<select id="sourceCatalogPriority">
<option value="">all priorities</option>
<option value="P0">P0</option>
<option value="P0 fallback">P0 fallback</option>
<option value="P1">P1</option>
<option value="P2">P2</option>
<option value="P3">P3</option>
<option value="P4">P4</option>
<option value="P5">P5</option>
</select>
</div>
<div class="source-catalog-actions">
<button type="button" id="importSourceCatalogBtn">Import catalog</button>
<button type="button" id="importIngestableSourcesBtn">Import feed seeds</button>
</div>
<div id="sourceCatalog"></div>
</div>
</details>
<details class="nested-section" data-sidebar-section="gtfs-workflow" open>
<summary><h3>GTFS workflow</h3></summary>
<div class="nested-section-body">
<div class="qa-toolbar">
<button type="button" id="refreshGtfsWorkflowBtn">Refresh workflow</button>
</div>
<div id="gtfsWorkflow" class="gtfs-workflow muted">Workflow status loading...</div>
</div>
</details>
<details class="nested-section" data-sidebar-section="gtfs-feed-qa" open>
<summary><h3>GTFS feed QA</h3></summary>
<div class="nested-section-body">
<div class="qa-toolbar">
<button type="button" id="refreshGtfsHarmonizationBtn">Refresh feeds</button>
</div>
<div id="gtfsHarmonizationInventory" class="harmonization-inventory muted">No GTFS feed QA loaded.</div>
</div>
</details>
<details class="nested-section" data-sidebar-section="map-gtfs-workbench" open>
<summary><h3>Map/GTFS workbench</h3></summary>
<div class="nested-section-body">
<p class="muted">Review and persist GTFS-to-map decisions here. Run the ordered workflow from the GTFS workflow panel above.</p>
<div class="qa-toolbar">
<button type="button" id="refreshMapGtfsWorkbenchBtn">Refresh queue</button>
</div>
<div class="filter-row">
<select id="mapGtfsReviewQueueFilter">
<option value="">all queues</option>
<option value="gtfs_deduplication">GTFS deduplication</option>
<option value="gtfs_update_diff">GTFS update diff</option>
<option value="route_matching">route matching</option>
<option value="stop_matching">stop matching</option>
</select>
<select id="mapGtfsReviewStatus">
<option value="open">open</option>
<option value="in_review">in review</option>
<option value="resolved">resolved</option>
<option value="dismissed">dismissed</option>
<option value="">all statuses</option>
</select>
</div>
<div class="filter-row">
<select id="mapGtfsReviewSeverityFilter">
<option value="">all severities</option>
<option value="error">error</option>
<option value="warn">warn</option>
<option value="info">info</option>
</select>
<select id="mapGtfsReviewModeFilter">
<option value="">all modes</option>
<option value="train">train</option>
<option value="tram">tram</option>
<option value="subway">subway</option>
<option value="bus">bus</option>
<option value="ferry">ferry</option>
</select>
</div>
<div class="filter-row">
<input id="mapGtfsReviewSearch" placeholder="Search review queue" />
</div>
<div id="mapGtfsReviewQueue" class="map-gtfs-review-queue muted">No workbench review queue loaded.</div>
</div>
</details>
<details class="nested-section" data-sidebar-section="gtfs-source-management" open>
<summary><h3>Registered GTFS feeds</h3></summary>
<div class="nested-section-body">
<div class="filter-row">
<input id="sourceSearch" placeholder="Filter registered feeds" />
</div>
<div id="sources"></div>
</div>
</details>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="mapping" open>
<summary><h2>Mapping Data</h2></summary>
<div class="sidebar-section-body">
<details class="nested-section" data-sidebar-section="add-map-source">
<summary><h3>Add map source</h3></summary>
<div class="nested-section-body">
<form id="mappingSourceForm">
<input name="catalog_entry_id" type="hidden" />
<label>Name <input name="name" required placeholder="Germany OSM PBF" /></label>
<label>Kind
<select name="kind">
<option value="osm_pbf">OSM PBF extract</option>
<option value="osm_geojson">OSM transport GeoJSON</option>
<option value="osm_diff">OSM change diff</option>
</select>
</label>
<label>URL or path <input name="url" required placeholder="https://.../latest.osm.pbf or ./data/routes.geojson" /></label>
<label>Country <input name="country" placeholder="DE" maxlength="8" /></label>
<label>License <input name="license" placeholder="ODbL / CC-BY / unknown" /></label>
<button type="submit">Add map source</button>
</form>
</div>
</details>
<details class="nested-section source-catalog-card" data-sidebar-section="geofabrik">
<summary><h3>Geofabrik OSM</h3></summary>
<div class="nested-section-body">
<div class="filter-row geofabrik-filter">
<input id="geofabrikSearch" placeholder="Berlin, Germany, Hamburg" />
<button type="button" id="geofabrikSearchBtn">Search</button>
</div>
<label class="inline-check"><input id="geofabrikDiffSource" type="checkbox" checked /> add diff source metadata</label>
<div id="geofabrikResults" class="dataset-search-results muted">Search Geofabrik extracts, then add or import one as an OSM PBF source.</div>
</div>
</details>
<details class="nested-section" data-sidebar-section="mapping-source-management" open>
<summary><h3>Map source library</h3></summary>
<div class="nested-section-body">
<div class="filter-row">
<input id="mappingSourceSearch" placeholder="Filter map sources" />
<select id="mappingSourceKindFilter">
<option value="">all map kinds</option>
<option value="osm_geojson">OSM GeoJSON</option>
<option value="osm_pbf">OSM PBF</option>
<option value="osm_diff">OSM diff</option>
</select>
</div>
<div id="mappingSources"></div>
</div>
</details>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="datasets" open>
<summary><h2>Datasets</h2></summary>
<div class="sidebar-section-body">
<details class="nested-section" data-sidebar-section="dataset-pipeline" open>
<summary><h3>Derivation pipeline</h3></summary>
<div class="nested-section-body">
<div class="workflow-actions">
<button id="loadSampleBtn" type="button">Reset sample</button>
</div>
<p class="muted">Matcher and route-layer controls live in the Map/GTFS workbench so review actions stay in one workflow.</p>
</div>
</details>
<details class="nested-section" data-sidebar-section="dataset-search">
<summary><h3>Dataset search</h3></summary>
<div class="nested-section-body">
<form id="datasetSearchForm" class="dataset-search-form">
<input id="datasetSearchQuery" placeholder="Route, line, stop, shape ID" autocomplete="off" />
<div class="filter-row">
<label class="inline-check"><input id="datasetSearchActiveOnly" type="checkbox" checked /> active only</label>
<button type="submit">Search</button>
</div>
</form>
<div id="datasetSearchResults" class="dataset-search-results muted">Search all imported datasets by label, route ID, and route-layer reference.</div>
</div>
</details>
<details class="nested-section matches-card" data-sidebar-section="route-matches">
<summary><h3>Raw route matches</h3></summary>
<div class="nested-section-body">
<p class="muted">Inspection/debug view of raw matcher rows. Use the Map/GTFS workbench for review decisions.</p>
<div class="filter-row">
<select id="matchStatusFilter">
<option value="">all</option>
<option value="matched">matched</option>
<option value="probable">probable</option>
<option value="weak">weak</option>
<option value="missing">missing</option>
<option value="accepted">accepted</option>
<option value="rejected">rejected</option>
</select>
<button id="reloadMatchesBtn">Reload</button>
</div>
<div id="matches"></div>
</div>
</details>
<details class="nested-section" data-sidebar-section="maintenance">
<summary><h3>Maintenance</h3></summary>
<div class="nested-section-body">
<div class="maintenance-grid">
<button type="button" data-admin-action="init-db">Init DB</button>
<button type="button" data-admin-action="backfill-gtfs-shapes">Backfill GTFS shapes</button>
<button type="button" data-admin-action="prune-cache-dry">Check cache</button>
<button type="button" data-admin-action="prune-cache">Prune cache</button>
<button type="button" data-admin-action="prune-inactive-dry">Check inactive</button>
<button type="button" data-admin-action="prune-inactive">Prune inactive</button>
<button type="button" data-admin-action="vacuum-db">Vacuum DB</button>
<button type="button" class="danger" data-admin-action="reset-db">Reset DB</button>
</div>
<div id="adminStatus" class="admin-status muted"></div>
</div>
</details>
</div>
</details>
<details class="card sidebar-section" data-sidebar-section="layers" open>
<summary><h2>Layers</h2></summary>
<div class="sidebar-section-body">
<div class="preset-row">
<button type="button" data-layer-preset="network">Network</button>
<button type="button" data-layer-preset="review">Matched/unmatched</button>
<button type="button" data-layer-preset="unmatched">Unmatched</button>
<button type="button" data-layer-preset="all">All</button>
</div>
<div id="layerControls" class="layer-controls"></div>
<div id="mapStatus" class="map-status muted"></div>
</div>
</details>
</div>
<button id="sidebarCollapseBtn" class="sidebar-collapse-handle" type="button" aria-label="Collapse left panel" title="Collapse left panel" aria-expanded="true"></button>
</aside>
<section class="map-panel">
<div id="map"></div>
<div id="mapLoading" class="map-loading" hidden>
<span class="spinner" aria-hidden="true"></span>
<span id="mapLoadingText">Loading map layers...</span>
</div>
<section class="map-floating journey-card">
<h2>Journey</h2>
<form id="journeyForm">
<div id="journeyTransitSnapshot" class="journey-snapshot muted">Transit snapshot loading...</div>
<label>From <input id="journeyFromQuery" placeholder="Hauptbahnhof" autocomplete="off" /></label>
<input id="journeyFromStop" type="hidden" />
<div id="journeyFromSuggestions" class="stop-suggestions"></div>
<button type="button" id="journeySwapBtn" class="journey-swap" title="Switch start and destination">Swap</button>
<label>To <input id="journeyToQuery" placeholder="Alexanderplatz" autocomplete="off" /></label>
<input id="journeyToStop" type="hidden" />
<div id="journeyToSuggestions" class="stop-suggestions"></div>
<label>Via <input id="journeyViaQuery" placeholder="optional stop" autocomplete="off" /></label>
<input id="journeyViaStop" type="hidden" />
<div id="journeyViaSuggestions" class="stop-suggestions"></div>
<div class="journey-mode" role="radiogroup" aria-label="Route mode">
<label><input type="radio" name="journeyMode" value="transit" checked /> Public transport</label>
<label><input type="radio" name="journeyMode" value="walk" /> Walk</label>
<label><input type="radio" name="journeyMode" value="bike" /> Bike</label>
<label><input type="radio" name="journeyMode" value="drive" /> Car</label>
</div>
<div class="journey-options">
<label>Date <input id="journeyServiceDate" type="date" /></label>
<label>Departure <input id="journeyDeparture" type="time" value="08:00" /></label>
<label>Transfer buffer <input id="journeyTransferMinutes" type="number" min="0" max="60" step="1" value="2" /></label>
<label>Rank by
<select id="journeyRanking">
<option value="recommended">Recommended</option>
<option value="earliest_arrival">Earliest arrival</option>
<option value="duration">Shortest duration</option>
<option value="fewest_transfers">Fewest transfers</option>
</select>
</label>
</div>
<label class="journey-direct"><input id="journeyDirectOnly" type="checkbox" /> Direct public transport only</label>
<div class="journey-actions">
<button type="button" id="journeyEarlierBtn">Earlier</button>
<button type="submit" class="primary">Search</button>
<button type="button" id="journeyLaterBtn">Later</button>
</div>
<button type="button" id="generateItinerariesBtn">Generate travel options</button>
</form>
<div id="journeyResults" class="journey-results"></div>
<section class="itinerary-panel">
<div class="journey-title">
<span>Comparison</span>
<button type="button" id="reloadItinerariesBtn">Reload</button>
</div>
<div id="itineraryResults" class="itinerary-results muted">Generate travel options to compare route families.</div>
</section>
</section>
<div class="legend">
<span><b class="line osm"></b>OSM existing routes</span>
<span><b class="line gtfs"></b>GTFS covered routes</span>
<span><b class="line missing"></b>GTFS missing OSM match</span>
<span><b class="dot stops"></b>Stops / stations / terminals</span>
</div>
</section>
</main>
<div id="overlay" class="overlay" hidden>
<section class="overlay-panel">
<div class="overlay-title">
<h2 id="overlayTitle">Candidates</h2>
<button id="overlayCloseBtn">Close</button>
</div>
<div id="overlayContent"></div>
</section>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" crossorigin=""></script>
<script src="/static/app.js?v={{ static_version }}"></script>
</body>
</html>