Add page to list all flask endpoints with filter
This commit is contained in:
43
app.py
43
app.py
@@ -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()
|
||||
|
||||
@@ -108,26 +108,11 @@
|
||||
</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"
|
||||
_="on click add .hidden to #sidebar then remove .ml-64 from #main
|
||||
on htmx:afterRequest go to the top of the body">
|
||||
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">
|
||||
<path
|
||||
@@ -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
23
templates/endpoints.html
Normal 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 %}
|
||||
94
templates/partials/endpoints_table.html
Normal file
94
templates/partials/endpoints_table.html
Normal 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>
|
||||
Reference in New Issue
Block a user