mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Simplified the code for the permission debug pages
Decided not to use as much JavaScript Used Codex CLI for this. Refs #2543
This commit is contained in:
parent
73014abe8b
commit
b018eb3171
6 changed files with 44 additions and 166 deletions
|
|
@ -40,26 +40,6 @@ function populateFormFromURL() {
|
|||
return params;
|
||||
}
|
||||
|
||||
// Update URL with current form values
|
||||
function updateURL(formId, page = 1) {
|
||||
const form = document.getElementById(formId);
|
||||
const formData = new FormData(form);
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (value) {
|
||||
params.append(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
params.set('page', page.toString());
|
||||
}
|
||||
|
||||
const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
|
||||
window.history.pushState({}, '', newURL);
|
||||
}
|
||||
|
||||
// HTML escape function
|
||||
function escapeHtml(text) {
|
||||
if (text === null || text === undefined) return '';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="permission-form">
|
||||
<form id="allowed-form">
|
||||
<form id="allowed-form" method="get" action="{{ urls.path("-/allowed") }}">
|
||||
<div class="form-section">
|
||||
<label for="action">Action (permission name):</label>
|
||||
<select id="action" name="action" required>
|
||||
|
|
@ -83,23 +83,6 @@ const resultsCount = document.getElementById('results-count');
|
|||
const pagination = document.getElementById('pagination');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const hasDebugPermission = {{ 'true' if has_debug_permission else 'false' }};
|
||||
let currentData = null;
|
||||
|
||||
form.addEventListener('submit', async (ev) => {
|
||||
ev.preventDefault();
|
||||
updateURL('allowed-form', 1);
|
||||
await fetchResults(1, false);
|
||||
});
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const params = populateFormFromURL();
|
||||
const action = params.get('action');
|
||||
const page = params.get('page');
|
||||
if (action) {
|
||||
fetchResults(page ? parseInt(page) : 1, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate form on initial load
|
||||
(function() {
|
||||
|
|
@ -107,11 +90,11 @@ window.addEventListener('popstate', () => {
|
|||
const action = params.get('action');
|
||||
const page = params.get('page');
|
||||
if (action) {
|
||||
fetchResults(page ? parseInt(page) : 1, false);
|
||||
fetchResults(page ? parseInt(page) : 1);
|
||||
}
|
||||
})();
|
||||
|
||||
async function fetchResults(page = 1, updateHistory = true) {
|
||||
async function fetchResults(page = 1) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Loading...';
|
||||
|
||||
|
|
@ -139,7 +122,6 @@ async function fetchResults(page = 1, updateHistory = true) {
|
|||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentData = data;
|
||||
displayResults(data);
|
||||
} else {
|
||||
displayError(data);
|
||||
|
|
@ -198,13 +180,8 @@ function displayResults(data) {
|
|||
if (data.previous_url || data.next_url) {
|
||||
if (data.previous_url) {
|
||||
const prevLink = document.createElement('a');
|
||||
prevLink.href = '#';
|
||||
prevLink.href = data.previous_url;
|
||||
prevLink.textContent = '← Previous';
|
||||
prevLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
updateURL('allowed-form', data.page - 1);
|
||||
fetchResults(data.page - 1, false);
|
||||
});
|
||||
pagination.appendChild(prevLink);
|
||||
}
|
||||
|
||||
|
|
@ -214,13 +191,8 @@ function displayResults(data) {
|
|||
|
||||
if (data.next_url) {
|
||||
const nextLink = document.createElement('a');
|
||||
nextLink.href = '#';
|
||||
nextLink.href = data.next_url;
|
||||
nextLink.textContent = 'Next →';
|
||||
nextLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
updateURL('allowed-form', data.page + 1);
|
||||
fetchResults(data.page + 1, false);
|
||||
});
|
||||
pagination.appendChild(nextLink);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="permission-form">
|
||||
<form id="check-form">
|
||||
<form id="check-form" method="get" action="{{ urls.path("-/check") }}">
|
||||
<div class="form-section">
|
||||
<label for="action">Action (permission name):</label>
|
||||
<select id="action" name="action" required>
|
||||
|
|
@ -159,21 +159,6 @@ async function performCheck() {
|
|||
}
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (ev) => {
|
||||
ev.preventDefault();
|
||||
updateURL('check-form');
|
||||
await performCheck();
|
||||
});
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const params = populateFormFromURL();
|
||||
const action = params.get('action');
|
||||
if (action) {
|
||||
performCheck();
|
||||
}
|
||||
});
|
||||
|
||||
// Populate form on initial load
|
||||
(function() {
|
||||
const params = populateFormFromURL();
|
||||
|
|
|
|||
|
|
@ -90,19 +90,13 @@ var permissions = Object.fromEntries(rawPerms.map(p => [p.name, p]));
|
|||
var permissionSelect = document.getElementById('permission');
|
||||
var resource1 = document.getElementById('resource_1');
|
||||
var resource2 = document.getElementById('resource_2');
|
||||
var resource1Section = resource1.closest('.form-section');
|
||||
var resource2Section = resource2.closest('.form-section');
|
||||
function updateResourceVisibility() {
|
||||
var permission = permissionSelect.value;
|
||||
var {takes_parent, takes_child} = permissions[permission];
|
||||
if (takes_parent) {
|
||||
resource1.closest('p').style.display = 'block';
|
||||
} else {
|
||||
resource1.closest('p').style.display = 'none';
|
||||
}
|
||||
if (takes_child) {
|
||||
resource2.closest('p').style.display = 'block';
|
||||
} else {
|
||||
resource2.closest('p').style.display = 'none';
|
||||
}
|
||||
resource1Section.style.display = takes_parent ? 'block' : 'none';
|
||||
resource2Section.style.display = takes_child ? 'block' : 'none';
|
||||
}
|
||||
permissionSelect.addEventListener('change', updateResourceVisibility);
|
||||
updateResourceVisibility();
|
||||
|
|
@ -113,14 +107,21 @@ var debugResult = document.getElementById('debugResult');
|
|||
debugPost.addEventListener('submit', function(ev) {
|
||||
ev.preventDefault();
|
||||
var formData = new FormData(debugPost);
|
||||
console.log(formData);
|
||||
fetch(debugPost.action, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams(formData),
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}).then(function(response) {
|
||||
if (!response.ok) {
|
||||
throw new Error('Request failed with status ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
}).then(function(data) {
|
||||
debugResult.innerText = JSON.stringify(data, null, 4);
|
||||
}).catch(function(error) {
|
||||
debugResult.innerText = JSON.stringify({ error: error.message }, null, 4);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div class="permission-form">
|
||||
<form id="rules-form">
|
||||
<form id="rules-form" method="get" action="{{ urls.path("-/rules") }}">
|
||||
<div class="form-section">
|
||||
<label for="action">Action (permission name):</label>
|
||||
<select id="action" name="action" required>
|
||||
|
|
@ -70,23 +70,6 @@ const resultsContent = document.getElementById('results-content');
|
|||
const resultsCount = document.getElementById('results-count');
|
||||
const pagination = document.getElementById('pagination');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
let currentData = null;
|
||||
|
||||
form.addEventListener('submit', async (ev) => {
|
||||
ev.preventDefault();
|
||||
updateURL('rules-form', 1);
|
||||
await fetchResults(1, false);
|
||||
});
|
||||
|
||||
// Handle browser back/forward
|
||||
window.addEventListener('popstate', () => {
|
||||
const params = populateFormFromURL();
|
||||
const action = params.get('action');
|
||||
const page = params.get('page');
|
||||
if (action) {
|
||||
fetchResults(page ? parseInt(page) : 1, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate form on initial load
|
||||
(function() {
|
||||
|
|
@ -94,11 +77,11 @@ window.addEventListener('popstate', () => {
|
|||
const action = params.get('action');
|
||||
const page = params.get('page');
|
||||
if (action) {
|
||||
fetchResults(page ? parseInt(page) : 1, false);
|
||||
fetchResults(page ? parseInt(page) : 1);
|
||||
}
|
||||
})();
|
||||
|
||||
async function fetchResults(page = 1, updateHistory = true) {
|
||||
async function fetchResults(page = 1) {
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Loading...';
|
||||
|
||||
|
|
@ -126,7 +109,6 @@ async function fetchResults(page = 1, updateHistory = true) {
|
|||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentData = data;
|
||||
displayResults(data);
|
||||
} else {
|
||||
displayError(data);
|
||||
|
|
@ -183,13 +165,8 @@ function displayResults(data) {
|
|||
if (data.previous_url || data.next_url) {
|
||||
if (data.previous_url) {
|
||||
const prevLink = document.createElement('a');
|
||||
prevLink.href = '#';
|
||||
prevLink.href = data.previous_url;
|
||||
prevLink.textContent = '← Previous';
|
||||
prevLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
updateURL('rules-form', data.page - 1);
|
||||
fetchResults(data.page - 1, false);
|
||||
});
|
||||
pagination.appendChild(prevLink);
|
||||
}
|
||||
|
||||
|
|
@ -199,22 +176,14 @@ function displayResults(data) {
|
|||
|
||||
if (data.next_url) {
|
||||
const nextLink = document.createElement('a');
|
||||
nextLink.href = '#';
|
||||
nextLink.href = data.next_url;
|
||||
nextLink.textContent = 'Next →';
|
||||
nextLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
updateURL('rules-form', data.page + 1);
|
||||
fetchResults(data.page + 1, false);
|
||||
});
|
||||
pagination.appendChild(nextLink);
|
||||
}
|
||||
}
|
||||
|
||||
// Update raw JSON
|
||||
document.getElementById('raw-json').innerHTML = jsonFormatHighlight(data);
|
||||
|
||||
// Scroll to results
|
||||
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
function displayError(data) {
|
||||
|
|
@ -225,8 +194,6 @@ function displayError(data) {
|
|||
resultsContent.innerHTML = `<div class="error-message">Error: ${escapeHtml(data.error || 'Unknown error')}</div>`;
|
||||
|
||||
document.getElementById('raw-json').innerHTML = jsonFormatHighlight(data);
|
||||
|
||||
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -180,35 +180,13 @@ class PermissionsDebugView(BaseView):
|
|||
vars = await request.post_vars()
|
||||
actor = json.loads(vars["actor"])
|
||||
permission = vars["permission"]
|
||||
resource_1 = vars["resource_1"]
|
||||
resource_2 = vars["resource_2"]
|
||||
parent = vars.get("resource_1") or None
|
||||
child = vars.get("resource_2") or None
|
||||
|
||||
# Use the action's properties to create the appropriate resource object
|
||||
action = self.ds.actions.get(permission)
|
||||
if not action:
|
||||
return Response.json({"error": f"Unknown action: {permission}"}, status=400)
|
||||
|
||||
if action.takes_parent and action.takes_child:
|
||||
resource_obj = action.resource_class(database=resource_1, table=resource_2)
|
||||
resource_for_response = (resource_1, resource_2)
|
||||
elif action.takes_parent:
|
||||
resource_obj = action.resource_class(database=resource_1)
|
||||
resource_for_response = resource_1
|
||||
else:
|
||||
resource_obj = action.resource_class()
|
||||
resource_for_response = None
|
||||
|
||||
result = await self.ds.allowed(
|
||||
action=permission, resource=resource_obj, actor=actor
|
||||
)
|
||||
return Response.json(
|
||||
{
|
||||
"actor": actor,
|
||||
"permission": permission,
|
||||
"resource": resource_for_response,
|
||||
"result": result,
|
||||
}
|
||||
response, status = await _check_permission_for_actor(
|
||||
self.ds, permission, parent, child, actor
|
||||
)
|
||||
return Response.json(response, status=status)
|
||||
|
||||
|
||||
class AllowedResourcesView(BaseView):
|
||||
|
|
@ -255,34 +233,35 @@ class AllowedResourcesView(BaseView):
|
|||
},
|
||||
)
|
||||
|
||||
# JSON API - action parameter is required
|
||||
payload, status = await self._allowed_payload(request, has_debug_permission)
|
||||
headers = {}
|
||||
if self.ds.cors:
|
||||
add_cors_headers(headers)
|
||||
return Response.json(payload, status=status, headers=headers)
|
||||
|
||||
async def _allowed_payload(self, request, has_debug_permission):
|
||||
action = request.args.get("action")
|
||||
if not action:
|
||||
return Response.json({"error": "action parameter is required"}, status=400)
|
||||
return {"error": "action parameter is required"}, 400
|
||||
if action not in self.ds.actions:
|
||||
return Response.json({"error": f"Unknown action: {action}"}, status=404)
|
||||
return {"error": f"Unknown action: {action}"}, 404
|
||||
|
||||
actor = request.actor if isinstance(request.actor, dict) else None
|
||||
actor_id = actor.get("id") if actor else None
|
||||
parent_filter = request.args.get("parent")
|
||||
child_filter = request.args.get("child")
|
||||
if child_filter and not parent_filter:
|
||||
return Response.json(
|
||||
{"error": "parent must be provided when child is specified"},
|
||||
status=400,
|
||||
)
|
||||
return {"error": "parent must be provided when child is specified"}, 400
|
||||
|
||||
try:
|
||||
page = int(request.args.get("page", "1"))
|
||||
page_size = int(request.args.get("page_size", "50"))
|
||||
except ValueError:
|
||||
return Response.json(
|
||||
{"error": "page and page_size must be integers"}, status=400
|
||||
)
|
||||
return {"error": "page and page_size must be integers"}, 400
|
||||
if page < 1:
|
||||
return Response.json({"error": "page must be >= 1"}, status=400)
|
||||
return {"error": "page must be >= 1"}, 400
|
||||
if page_size < 1:
|
||||
return Response.json({"error": "page_size must be >= 1"}, status=400)
|
||||
return {"error": "page_size must be >= 1"}, 400
|
||||
max_page_size = 200
|
||||
if page_size > max_page_size:
|
||||
page_size = max_page_size
|
||||
|
|
@ -304,10 +283,7 @@ class AllowedResourcesView(BaseView):
|
|||
)
|
||||
except Exception:
|
||||
# If catalog tables don't exist yet, return empty results
|
||||
headers = {}
|
||||
if self.ds.cors:
|
||||
add_cors_headers(headers)
|
||||
return Response.json(
|
||||
return (
|
||||
{
|
||||
"action": action,
|
||||
"actor_id": actor_id,
|
||||
|
|
@ -316,7 +292,7 @@ class AllowedResourcesView(BaseView):
|
|||
"total": 0,
|
||||
"items": [],
|
||||
},
|
||||
headers=headers,
|
||||
200,
|
||||
)
|
||||
|
||||
# Convert to list of dicts with resource path
|
||||
|
|
@ -396,10 +372,7 @@ class AllowedResourcesView(BaseView):
|
|||
if page > 1:
|
||||
response["previous_url"] = build_page_url(page - 1)
|
||||
|
||||
headers = {}
|
||||
if self.ds.cors:
|
||||
add_cors_headers(headers)
|
||||
return Response.json(response, headers=headers)
|
||||
return response, 200
|
||||
|
||||
|
||||
class PermissionRulesView(BaseView):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue