From a5ed364a46f4e0c96739bc2c9607b39532d2e4e0 Mon Sep 17 00:00:00 2001 From: "benjamin.baker" Date: Fri, 28 Aug 2026 17:40:31 -0400 Subject: [PATCH] Resolve Fleet list route without a trailing slash The Fleet list is the only endpoints route on the bare blueprint prefix, so it registered as /api/endpoints/ while the SPA calls /api/endpoints. With strict_slashes on, Flask 308-redirects to add the slash; behind the /shopdb reverse proxy (IIS ARR) that redirect Location drops the path prefix, so the list call fails while suffix routes like /api/endpoints/ keep working. That is why a healthy, reporting endpoint showed on its PC detail page but never in Fleet. strict_slashes=False makes both URL forms resolve directly and removes the fragile redirect. --- plugins/endpoints/api/routes.py | 2 +- plugins/endpoints/tests/test_plugin.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/plugins/endpoints/api/routes.py b/plugins/endpoints/api/routes.py index 07355173..7865cadd 100644 --- a/plugins/endpoints/api/routes.py +++ b/plugins/endpoints/api/routes.py @@ -55,7 +55,7 @@ def _format_uptime(seconds): # Endpoint state # ============================================================================= -@endpoints_bp.route('/', methods=['GET']) +@endpoints_bp.route('/', methods=['GET'], strict_slashes=False) @require_permission('endpoints.view') def list_endpoints(): """List reported endpoint states. diff --git a/plugins/endpoints/tests/test_plugin.py b/plugins/endpoints/tests/test_plugin.py index 6001ab5f..423eb5dc 100644 --- a/plugins/endpoints/tests/test_plugin.py +++ b/plugins/endpoints/tests/test_plugin.py @@ -169,3 +169,25 @@ def test_telemetrymeta_route_is_registered_for_freshness_gating(): application.register_blueprint(EndpointsPlugin().get_blueprint()) rules = {rule.rule for rule in application.url_map.iter_rules()} assert any(rule.endswith('/asset//telemetrymeta') for rule in rules) + + +def test_fleet_list_route_resolves_without_trailing_slash(): + """The Fleet list route must match the slash-less URL the SPA calls. + + The list is the only route on the bare blueprint prefix. If it demands a + trailing slash, Flask 308-redirects /api/endpoints to /api/endpoints/, and + behind the /shopdb reverse proxy that redirect drops the path prefix and + the list call fails while the detail routes (which carry a path suffix) + keep working. strict_slashes=False makes both forms resolve directly. + """ + from flask import Flask + + application = Flask(__name__) + application.register_blueprint( + EndpointsPlugin().get_blueprint(), url_prefix='/api/endpoints') + adapter = application.url_map.bind('localhost') + + endpoint, _ = adapter.match('/api/endpoints', method='GET') + assert endpoint.endswith('list_endpoints') + endpoint_slash, _ = adapter.match('/api/endpoints/', method='GET') + assert endpoint_slash.endswith('list_endpoints')