Add page to list all flask endpoints with filter

This commit is contained in:
Peter Stockings
2024-11-09 19:07:55 +11:00
parent c7013e0eac
commit 120a94ff45
4 changed files with 188 additions and 16 deletions

43
app.py
View File

@@ -534,6 +534,49 @@ def plot_query(query_id):
plot_div = generate_plot(results_df, title)
return plot_div
def get_routes():
return routes
@app.route('/endpoints')
def list_endpoints():
"""
Lists all API endpoints available in the Flask application.
This endpoint retrieves all registered routes, excluding static routes,
and displays their details such as endpoint name, URL, allowed HTTP methods,
view function name, and a brief description.
"""
routes = []
for rule in app.url_map.iter_rules():
if rule.endpoint == 'static':
continue
methods = ', '.join(sorted(rule.methods - {'HEAD', 'OPTIONS'}))
route_info = {
'endpoint': rule.endpoint,
'url': rule.rule,
'methods': methods,
'view_func': app.view_functions[rule.endpoint].__name__,
'doc': app.view_functions[rule.endpoint].__doc__
}
routes.append(route_info)
search = request.args.get('search', '').lower()
if search:
routes = [
route for route in routes
if search in route['endpoint'].lower()
or search in route['url'].lower()
or search in route['methods'].lower()
or search in route['view_func'].lower()
or (route['doc'] and search in route['doc'].lower())
]
if htmx:
return render_template('partials/endpoints_table.html', routes=routes)
return render_template('endpoints.html', routes=routes)
@app.teardown_appcontext
def closeConnection(exception):
db.close_connection()

View File

@@ -108,21 +108,6 @@
</div>
</ul>
<div class="space-y-2 pt-2">
<a hx-get="{{ url_for('settings') }}" hx-push-url="true" hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('settings')) }} page-link"
_="on click add .hidden to #sidebar then remove .ml-64 from #main
on htmx:afterRequest go to the top of the body">
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
<path fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd"></path>
</svg>
<span class="ml-3">Settings</span>
</a>
</div>
<div class="space-y-2 pt-2">
<a hx-get="{{ url_for('sql_explorer') }}" hx-push-url="true" hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('sql_explorer')) }} page-link"
@@ -138,6 +123,33 @@
</svg>
<span class="ml-3">SQL Explorer</span>
</a>
<a hx-get="{{ url_for('list_endpoints') }}" hx-push-url="true" hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('list_endpoints')) }} page-link"
_="on click add .hidden to #sidebar then remove .ml-64 from #main
on htmx:afterRequest go to the top of the body">
<!-- Server Icon from Heroicons -->
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M4 5a1 1 0 011-1h10a1 1 0 011 1v1H4V5z" />
<path fill-rule="evenodd"
d="M3 7a1 1 0 011-1h12a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1V7zm1 2v4h12V9H4z"
clip-rule="evenodd" />
</svg>
<span class="ml-3">Endpoints</span>
</a>
<a hx-get="{{ url_for('settings') }}" hx-push-url="true" hx-target="#container"
class="text-base text-gray-900 font-normal rounded-lg hover:bg-gray-100 group transition duration-75 flex items-center p-2 cursor-pointer {{ is_selected_page(url_for('settings')) }} page-link"
_="on click add .hidden to #sidebar then remove .ml-64 from #main
on htmx:afterRequest go to the top of the body">
<svg class="w-6 h-6 text-gray-500 group-hover:text-gray-900 transition duration-75"
fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"
data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;">
<path fill-rule="evenodd"
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
clip-rule="evenodd"></path>
</svg>
<span class="ml-3">Settings</span>
</a>
</div>
</div>
</div>

23
templates/endpoints.html Normal file
View File

@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mx-auto">
<!-- Title Section -->
<div class="mb-6">
<h1 class="text-4xl font-extrabold text-gray-800">API Endpoints Overview</h1>
<p class="mt-2 text-lg text-gray-600">{{ routes|length }} Routes Available</p>
</div>
<!-- Optional: Search or Filter functionality with HTMX -->
<div class="mb-4">
<input type="text" id="search" name="search" placeholder="Search endpoints..."
class="w-full p-2 border border-gray-300 rounded" hx-get="/endpoints"
hx-trigger="keyup changed delay:500ms, search" hx-push-url="true" hx-target="#endpoints-table"
hx-include="[name='search']">
</div>
<div id="endpoints-table">
{{ render_partial('partials/endpoints_table.html', routes=routes) }}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,94 @@
<div class="overflow-x-auto">
<!-- Column Toggle Checkboxes -->
<div class="mb-4 flex flex-wrap items-center space-x-4">
<label class="inline-flex items-center">
<input type="checkbox" checked class="form-checkbox h-5 w-5 text-blue-600" hx-target=".col-endpoint"
hx-trigger="change" _="
on change if me.checked then
remove .hidden from .col-endpoint
else
add .hidden to .col-endpoint
">
<span class="ml-2 text-gray-700">Endpoint</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" checked class="form-checkbox h-5 w-5 text-blue-600" hx-target=".col-url"
hx-trigger="change" _="
on change if me.checked then
remove .hidden from .col-url
else
add .hidden to .col-url
">
<span class="ml-2 text-gray-700">URL</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" checked class="form-checkbox h-5 w-5 text-blue-600" hx-target=".col-methods"
hx-trigger="change" _="
on change if me.checked then
remove .hidden from .col-methods
else
add .hidden to .col-methods
">
<span class="ml-2 text-gray-700">Methods</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" checked class="form-checkbox h-5 w-5 text-blue-600" hx-target=".col-view_func"
hx-trigger="change" _="
on change if me.checked then
remove .hidden from .col-view_func
else
add .hidden to .col-view_func
">
<span class="ml-2 text-gray-700">View Function</span>
</label>
<label class="inline-flex items-center">
<input type="checkbox" checked class="form-checkbox h-5 w-5 text-blue-600" hx-target=".col-description"
hx-trigger="change" _="
on change if me.checked then
remove .hidden from .col-description
else
add .hidden to .col-description
">
<span class="ml-2 text-gray-700">Description</span>
</label>
</div>
<!-- Endpoints Table -->
<table class="min-w-full bg-white rounded-lg shadow">
<thead class="bg-gray-200">
<tr>
<th class="py-2 px-4 border-b text-left text-sm font-medium text-gray-700 col-endpoint">Endpoint</th>
<th class="py-2 px-4 border-b text-left text-sm font-medium text-gray-700 col-url">URL</th>
<th class="py-2 px-4 border-b text-left text-sm font-medium text-gray-700 col-methods">Methods</th>
<th class="py-2 px-4 border-b text-left text-sm font-medium text-gray-700 col-view_func">View Function
</th>
<th class="py-2 px-4 border-b text-left text-sm font-medium text-gray-700 col-description">Description
</th>
</tr>
</thead>
<tbody>
{% for route in routes %}
<tr class="hover:bg-gray-100 even:bg-gray-50">
<td class="py-2 px-4 border-b text-sm text-gray-800 col-endpoint">{{ route.endpoint }}</td>
<td class="py-2 px-4 border-b text-sm text-gray-800 col-url">{{ route.url }}</td>
<td class="py-2 px-4 border-b text-sm text-gray-800 col-methods">{{ route.methods }}</td>
<td class="py-2 px-4 border-b text-sm text-gray-800 col-view_func">{{ route.view_func }}</td>
<td class="py-2 px-4 border-b text-sm text-gray-800 col-description">
{% if route.doc %}
<details class="group">
<summary class="cursor-pointer text-blue-600 hover:underline">
{{ route.doc.split('\n')[0] }}{% if route.doc.count('\n') > 0 %}...{% endif %}
</summary>
<div class="mt-2 text-gray-700">
{{ route.doc }}
</div>
</details>
{% else %}
N/A
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>