diff --git a/datasette/templates/debug_allowed.html b/datasette/templates/debug_allowed.html
index 9483926b..b06e1faf 100644
--- a/datasette/templates/debug_allowed.html
+++ b/datasette/templates/debug_allowed.html
@@ -42,7 +42,7 @@
- Filter results to a specific child resource (requires parent)
+ Filter results to a specific child resource (requires parent to be set)
@@ -236,12 +236,15 @@ function displayError(data) {
const parentInput = document.getElementById('parent');
const childInput = document.getElementById('child');
-childInput.addEventListener('focus', () => {
+parentInput.addEventListener('input', () => {
+ childInput.disabled = !parentInput.value;
if (!parentInput.value) {
- alert('Please specify a parent resource first before filtering by child resource.');
- parentInput.focus();
+ childInput.value = '';
}
});
+
+// Initialize disabled state
+childInput.disabled = !parentInput.value;
{% endblock %}
diff --git a/datasette/views/special.py b/datasette/views/special.py
index ca155a04..e896becc 100644
--- a/datasette/views/special.py
+++ b/datasette/views/special.py
@@ -189,22 +189,6 @@ class AllowedResourcesView(BaseView):
name = "allowed"
has_json_alternate = False
- CANDIDATE_SQL = {
- "view-table": (
- "SELECT database_name AS parent, table_name AS child FROM catalog_tables",
- {},
- ),
- "view-database": (
- "SELECT database_name AS parent, NULL AS child FROM catalog_databases",
- {},
- ),
- "view-instance": ("SELECT NULL AS parent, NULL AS child", {}),
- "execute-sql": (
- "SELECT database_name AS parent, NULL AS child FROM catalog_databases",
- {},
- ),
- }
-
async def get(self, request):
await self.ds.refresh_schemas()
@@ -218,11 +202,29 @@ class AllowedResourcesView(BaseView):
if not as_format:
# Render the HTML form (even if query parameters are present)
+ # Put most common/interesting actions first
+ priority_actions = [
+ "view-instance",
+ "view-database",
+ "view-table",
+ "view-query",
+ "execute-sql",
+ "insert-row",
+ "update-row",
+ "delete-row",
+ ]
+ actions = list(self.ds.actions.keys())
+ # Priority actions first (in order), then remaining alphabetically
+ sorted_actions = [a for a in priority_actions if a in actions]
+ sorted_actions.extend(
+ sorted(a for a in actions if a not in priority_actions)
+ )
+
return await self.render(
["debug_allowed.html"],
request,
{
- "supported_actions": sorted(self.CANDIDATE_SQL.keys()),
+ "supported_actions": sorted_actions,
},
)
@@ -232,11 +234,6 @@ class AllowedResourcesView(BaseView):
return Response.json({"error": "action parameter is required"}, status=400)
if action not in self.ds.actions:
return Response.json({"error": f"Unknown action: {action}"}, status=404)
- if action not in self.CANDIDATE_SQL:
- return Response.json(
- {"error": f"Action '{action}' is not supported by this endpoint"},
- status=400,
- )
actor = request.actor if isinstance(request.actor, dict) else None
actor_id = actor.get("id") if actor else None
diff --git a/tests/test_permission_endpoints.py b/tests/test_permission_endpoints.py
index a1aa4578..65280a06 100644
--- a/tests/test_permission_endpoints.py
+++ b/tests/test_permission_endpoints.py
@@ -69,8 +69,12 @@ async def ds_with_permissions():
("/-/allowed.json", 400, {"error"}),
# Invalid action
("/-/allowed.json?action=nonexistent", 404, {"error"}),
- # Unsupported action (valid but not in CANDIDATE_SQL)
- ("/-/allowed.json?action=insert-row", 400, {"error"}),
+ # Any valid action works, even if no permission rules exist for it
+ (
+ "/-/allowed.json?action=insert-row",
+ 200,
+ {"action", "items", "total", "page"},
+ ),
],
)
async def test_allowed_json_basic(