merge: fix admin template preview UX issues (maps, article_stats route, dev debug mode)
This commit is contained in:
@@ -7,7 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **Admin template preview maps** — Leaflet maps rendered blank in admin preview due to `.card` `overflow: hidden` clipping tile layers. Set `overflow: visible` on the rendered-article card. Also added `.catch()` handlers to map fetch calls so failures are logged to console.
|
- **Admin template preview maps** — Leaflet maps rendered blank because `article-maps.js` called `L.divIcon()` at the IIFE top level before Leaflet was dynamically loaded, crashing the script. Moved `VENUE_ICON` creation into the `script.onload` callback so it runs after Leaflet is available. Previous commit's `.card` `overflow: visible` fix remains (clips tile layers otherwise).
|
||||||
|
- **Admin articles page 500** — `/admin/articles` crashed with `BuildError` when an article generation task was running because `article_stats.html` partial referenced `url_for('admin.article_stats')` but the route didn't exist. Added the missing HTMX partial endpoint.
|
||||||
|
- **Silent 500 errors in dev** — `dev_run.sh` used Granian which swallowed Quart's debug error pages, showing generic "Internal Server Error" with no traceback. Switched to `uv run python -m padelnomics.app` for proper debug mode with browser tracebacks. Added `@app.errorhandler(500)` to log exceptions even when running under Granian in production.
|
||||||
- **Pipeline diagnostic script** (`scripts/check_pipeline.py`) — handle DuckDB catalog naming quirk where `lakehouse.duckdb` uses catalog `lakehouse` instead of `local`, causing SQLMesh logical views to break. Script now auto-detects the catalog via `USE`, and falls back to querying physical tables (`sqlmesh__<schema>.<table>__<hash>`) when views fail.
|
- **Pipeline diagnostic script** (`scripts/check_pipeline.py`) — handle DuckDB catalog naming quirk where `lakehouse.duckdb` uses catalog `lakehouse` instead of `local`, causing SQLMesh logical views to break. Script now auto-detects the catalog via `USE`, and falls back to querying physical tables (`sqlmesh__<schema>.<table>__<hash>`) when views fail.
|
||||||
- **Eurostat gas prices extractor** — `nrg_pc_203` filter missing `unit` dimension (API returns both KWH and GJ_GCV); now filters to `KWH`.
|
- **Eurostat gas prices extractor** — `nrg_pc_203` filter missing `unit` dimension (API returns both KWH and GJ_GCV); now filters to `KWH`.
|
||||||
- **Eurostat labour costs extractor** — `lc_lci_lev` used non-existent `currency` filter dimension; corrected to `unit: EUR`.
|
- **Eurostat labour costs extractor** — `lc_lci_lev` used non-existent `currency` filter dimension; corrected to `unit: EUR`.
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ echo ""
|
|||||||
echo "Press Ctrl-C to stop all processes."
|
echo "Press Ctrl-C to stop all processes."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
run_with_label "$COLOR_APP" "app " uv run granian --interface asgi --host 127.0.0.1 --port 5000 --reload --reload-paths web/src padelnomics.app:app
|
run_with_label "$COLOR_APP" "app " uv run python -m padelnomics.app
|
||||||
run_with_label "$COLOR_WORKER" "worker" uv run python -u -m padelnomics.worker
|
run_with_label "$COLOR_WORKER" "worker" uv run python -u -m padelnomics.worker
|
||||||
run_with_label "$COLOR_CSS" "css " make css-watch
|
run_with_label "$COLOR_CSS" "css " make css-watch
|
||||||
|
|
||||||
|
|||||||
@@ -2465,6 +2465,18 @@ async def articles():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/articles/stats")
|
||||||
|
@role_required("admin")
|
||||||
|
async def article_stats():
|
||||||
|
"""HTMX partial: article stats bar (polled while generating)."""
|
||||||
|
stats = await _get_article_stats()
|
||||||
|
return await render_template(
|
||||||
|
"admin/partials/article_stats.html",
|
||||||
|
stats=stats,
|
||||||
|
is_generating=await _is_generating(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/articles/results")
|
@bp.route("/articles/results")
|
||||||
@role_required("admin")
|
@role_required("admin")
|
||||||
async def article_results():
|
async def article_results():
|
||||||
|
|||||||
@@ -270,6 +270,13 @@ def create_app() -> Quart:
|
|||||||
from .sitemap import sitemap_response
|
from .sitemap import sitemap_response
|
||||||
return await sitemap_response(config.BASE_URL)
|
return await sitemap_response(config.BASE_URL)
|
||||||
|
|
||||||
|
# Ensure unhandled exceptions are always logged (Granian doesn't show
|
||||||
|
# Quart's debug error page, so without this 500s are silent).
|
||||||
|
@app.errorhandler(500)
|
||||||
|
async def handle_500(error):
|
||||||
|
app.logger.exception("Unhandled 500 error: %s", error)
|
||||||
|
return "Internal Server Error", 500
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
@app.route("/health")
|
@app.route("/health")
|
||||||
async def health():
|
async def health():
|
||||||
|
|||||||
@@ -62,14 +62,7 @@
|
|||||||
.catch(function(err) { console.error('Country map fetch failed:', err); });
|
.catch(function(err) { console.error('Country map fetch failed:', err); });
|
||||||
}
|
}
|
||||||
|
|
||||||
var VENUE_ICON = L.divIcon({
|
function initCityMap(el, venueIcon) {
|
||||||
className: '',
|
|
||||||
html: '<div class="pn-venue"></div>',
|
|
||||||
iconSize: [10, 10],
|
|
||||||
iconAnchor: [5, 5],
|
|
||||||
});
|
|
||||||
|
|
||||||
function initCityMap(el) {
|
|
||||||
var countrySlug = el.dataset.countrySlug;
|
var countrySlug = el.dataset.countrySlug;
|
||||||
var citySlug = el.dataset.citySlug;
|
var citySlug = el.dataset.citySlug;
|
||||||
var lat = parseFloat(el.dataset.lat);
|
var lat = parseFloat(el.dataset.lat);
|
||||||
@@ -91,7 +84,7 @@
|
|||||||
: '')
|
: '')
|
||||||
: '';
|
: '';
|
||||||
var tip = '<strong>' + v.name + '</strong>' + (courtLine ? '<br>' + courtLine : '');
|
var tip = '<strong>' + v.name + '</strong>' + (courtLine ? '<br>' + courtLine : '');
|
||||||
L.marker([v.lat, v.lon], { icon: VENUE_ICON })
|
L.marker([v.lat, v.lon], { icon: venueIcon })
|
||||||
.bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -7] })
|
.bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -7] })
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
});
|
});
|
||||||
@@ -104,7 +97,15 @@
|
|||||||
script.src = window.LEAFLET_JS_URL || '/static/vendor/leaflet/leaflet.min.js';
|
script.src = window.LEAFLET_JS_URL || '/static/vendor/leaflet/leaflet.min.js';
|
||||||
script.onload = function() {
|
script.onload = function() {
|
||||||
if (countryMapEl) initCountryMap(countryMapEl);
|
if (countryMapEl) initCountryMap(countryMapEl);
|
||||||
if (cityMapEl) initCityMap(cityMapEl);
|
if (cityMapEl) {
|
||||||
|
var venueIcon = L.divIcon({
|
||||||
|
className: '',
|
||||||
|
html: '<div class="pn-venue"></div>',
|
||||||
|
iconSize: [10, 10],
|
||||||
|
iconAnchor: [5, 5],
|
||||||
|
});
|
||||||
|
initCityMap(cityMapEl, venueIcon);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user