Fix minor irritation with /-/allowed UI

This commit is contained in:
Simon Willison 2025-10-25 18:02:26 -07:00
commit ee62bf9bdc
3 changed files with 32 additions and 28 deletions

View file

@ -42,7 +42,7 @@
<div class="form-section"> <div class="form-section">
<label for="child">Filter by child (optional):</label> <label for="child">Filter by child (optional):</label>
<input type="text" id="child" name="child" placeholder="e.g., table name"> <input type="text" id="child" name="child" placeholder="e.g., table name">
<small>Filter results to a specific child resource (requires parent)</small> <small>Filter results to a specific child resource (requires parent to be set)</small>
</div> </div>
<div class="form-section"> <div class="form-section">
@ -236,12 +236,15 @@ function displayError(data) {
const parentInput = document.getElementById('parent'); const parentInput = document.getElementById('parent');
const childInput = document.getElementById('child'); const childInput = document.getElementById('child');
childInput.addEventListener('focus', () => { parentInput.addEventListener('input', () => {
childInput.disabled = !parentInput.value;
if (!parentInput.value) { if (!parentInput.value) {
alert('Please specify a parent resource first before filtering by child resource.'); childInput.value = '';
parentInput.focus();
} }
}); });
// Initialize disabled state
childInput.disabled = !parentInput.value;
</script> </script>
{% endblock %} {% endblock %}

View file

@ -189,22 +189,6 @@ class AllowedResourcesView(BaseView):
name = "allowed" name = "allowed"
has_json_alternate = False 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): async def get(self, request):
await self.ds.refresh_schemas() await self.ds.refresh_schemas()
@ -218,11 +202,29 @@ class AllowedResourcesView(BaseView):
if not as_format: if not as_format:
# Render the HTML form (even if query parameters are present) # 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( return await self.render(
["debug_allowed.html"], ["debug_allowed.html"],
request, 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) return Response.json({"error": "action parameter is required"}, status=400)
if action not in self.ds.actions: if action not in self.ds.actions:
return Response.json({"error": f"Unknown action: {action}"}, status=404) 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 = request.actor if isinstance(request.actor, dict) else None
actor_id = actor.get("id") if actor else None actor_id = actor.get("id") if actor else None

View file

@ -69,8 +69,12 @@ async def ds_with_permissions():
("/-/allowed.json", 400, {"error"}), ("/-/allowed.json", 400, {"error"}),
# Invalid action # Invalid action
("/-/allowed.json?action=nonexistent", 404, {"error"}), ("/-/allowed.json?action=nonexistent", 404, {"error"}),
# Unsupported action (valid but not in CANDIDATE_SQL) # Any valid action works, even if no permission rules exist for it
("/-/allowed.json?action=insert-row", 400, {"error"}), (
"/-/allowed.json?action=insert-row",
200,
{"action", "items", "total", "page"},
),
], ],
) )
async def test_allowed_json_basic( async def test_allowed_json_basic(