kopia lustrzana https://github.com/simonw/datasette
Example links for API explorer, closes #1871
rodzic
c603faac5b
commit
db796771e2
|
@ -22,7 +22,7 @@
|
||||||
<form method="get" id="api-explorer-get" style="margin-top: 0.7em">
|
<form method="get" id="api-explorer-get" style="margin-top: 0.7em">
|
||||||
<div>
|
<div>
|
||||||
<label for="path">API path:</label>
|
<label for="path">API path:</label>
|
||||||
<input type="text" id="path" name="path" value="/data/docs.json" style="width: 60%">
|
<input type="text" id="path" name="path" style="width: 60%">
|
||||||
<input type="submit" value="GET">
|
<input type="submit" value="GET">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
<form method="post" id="api-explorer-post" style="margin-top: 0.7em">
|
<form method="post" id="api-explorer-post" style="margin-top: 0.7em">
|
||||||
<div>
|
<div>
|
||||||
<label for="path">API path:</label>
|
<label for="path">API path:</label>
|
||||||
<input type="text" id="path" name="path" value="/data/docs/-/insert" style="width: 60%">
|
<input type="text" id="path" name="path" style="width: 60%">
|
||||||
</div>
|
</div>
|
||||||
<div style="margin: 0.5em 0">
|
<div style="margin: 0.5em 0">
|
||||||
<label for="apiJson" style="vertical-align: top">JSON:</label>
|
<label for="apiJson" style="vertical-align: top">JSON:</label>
|
||||||
|
@ -65,8 +65,11 @@ var getForm = document.getElementById('api-explorer-get');
|
||||||
var output = document.getElementById('output');
|
var output = document.getElementById('output');
|
||||||
var errorList = output.querySelector('.errors');
|
var errorList = output.querySelector('.errors');
|
||||||
|
|
||||||
// On first load, populate forms from # in URL, if present
|
// On first load or fragment change populate forms from # in URL, if present
|
||||||
if (window.location.hash) {
|
if (window.location.hash) {
|
||||||
|
onFragmentChange();
|
||||||
|
}
|
||||||
|
function onFragmentChange() {
|
||||||
var hash = window.location.hash.slice(1);
|
var hash = window.location.hash.slice(1);
|
||||||
// Treat hash as a foo=bar string and parse it:
|
// Treat hash as a foo=bar string and parse it:
|
||||||
var params = new URLSearchParams(hash);
|
var params = new URLSearchParams(hash);
|
||||||
|
@ -82,6 +85,11 @@ if (window.location.hash) {
|
||||||
postForm.querySelector('textarea[name="json"]').value = params.get('json');
|
postForm.querySelector('textarea[name="json"]').value = params.get('json');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
onFragmentChange();
|
||||||
|
// Animate scroll to top of page
|
||||||
|
window.scrollTo({top: 0, behavior: 'smooth'});
|
||||||
|
});
|
||||||
|
|
||||||
// Cause GET and POST regions to toggle each other
|
// Cause GET and POST regions to toggle each other
|
||||||
var getDetails = getForm.closest('details');
|
var getDetails = getForm.closest('details');
|
||||||
|
@ -171,4 +179,27 @@ postForm.addEventListener("submit", (ev) => {
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{% if example_links %}
|
||||||
|
<h2>API endpoints</h2>
|
||||||
|
<ul class="bullets">
|
||||||
|
{% for database in example_links %}
|
||||||
|
<li>Database: <strong>{{ database.name }}</strong></li>
|
||||||
|
<ul class="bullets">
|
||||||
|
{% for link in database.links %}
|
||||||
|
<li><a href="{{ api_path(link) }}">{{ link.path }}</a> - {{ link.label }} </li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for table in database.tables %}
|
||||||
|
<li><strong>{{ table.name }}</strong>
|
||||||
|
<ul class="bullets">
|
||||||
|
{% for link in table.links %}
|
||||||
|
<li><a href="{{ api_path(link) }}">{{ link.path }}</a> - {{ link.label }} </li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -6,6 +6,7 @@ from datasette.permissions import PERMISSIONS
|
||||||
from .base import BaseView
|
from .base import BaseView
|
||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
|
||||||
class JsonDataView(BaseView):
|
class JsonDataView(BaseView):
|
||||||
|
@ -275,5 +276,115 @@ class ApiExplorerView(BaseView):
|
||||||
name = "api_explorer"
|
name = "api_explorer"
|
||||||
has_json_alternate = False
|
has_json_alternate = False
|
||||||
|
|
||||||
|
async def example_links(self, request):
|
||||||
|
databases = []
|
||||||
|
for name, db in self.ds.databases.items():
|
||||||
|
if name == "_internal":
|
||||||
|
continue
|
||||||
|
if not db.is_mutable:
|
||||||
|
continue
|
||||||
|
database_visible, _ = await self.ds.check_visibility(
|
||||||
|
request.actor,
|
||||||
|
"view-database",
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
if not database_visible:
|
||||||
|
continue
|
||||||
|
tables = []
|
||||||
|
table_names = await db.table_names()
|
||||||
|
for table in table_names:
|
||||||
|
visible, _ = await self.ds.check_visibility(
|
||||||
|
request.actor,
|
||||||
|
"view-table",
|
||||||
|
(name, table),
|
||||||
|
)
|
||||||
|
if not visible:
|
||||||
|
continue
|
||||||
|
table_links = []
|
||||||
|
table_links.append(
|
||||||
|
{
|
||||||
|
"label": "Get rows for {}".format(table),
|
||||||
|
"method": "GET",
|
||||||
|
"path": self.ds.urls.table(name, table, format="json")
|
||||||
|
+ "?_shape=objects".format(name, table),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if await self.ds.permission_allowed(
|
||||||
|
request.actor, "insert-row", (name, table)
|
||||||
|
):
|
||||||
|
pks = await db.primary_keys(table)
|
||||||
|
table_links.append(
|
||||||
|
{
|
||||||
|
"path": self.ds.urls.table(name, table) + "/-/insert",
|
||||||
|
"method": "POST",
|
||||||
|
"label": "Insert rows into {}".format(table),
|
||||||
|
"json": {
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
column: None
|
||||||
|
for column in await db.table_columns(table)
|
||||||
|
if column not in pks
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if await self.ds.permission_allowed(
|
||||||
|
request.actor, "drop-table", (name, table)
|
||||||
|
):
|
||||||
|
table_links.append(
|
||||||
|
{
|
||||||
|
"path": self.ds.urls.table(name, table) + "/-/drop",
|
||||||
|
"label": "Drop table {}".format(table),
|
||||||
|
"json": {},
|
||||||
|
"method": "POST",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tables.append({"name": table, "links": table_links})
|
||||||
|
database_links = []
|
||||||
|
if await self.ds.permission_allowed(request.actor, "create-table", name):
|
||||||
|
database_links.append(
|
||||||
|
{
|
||||||
|
"path": self.ds.urls.database(name) + "/-/create",
|
||||||
|
"label": "Create table in {}".format(name),
|
||||||
|
"json": {
|
||||||
|
"table": "new_table",
|
||||||
|
"columns": [
|
||||||
|
{"name": "id", "type": "integer"},
|
||||||
|
{"name": "name", "type": "text"},
|
||||||
|
],
|
||||||
|
"pk": "id",
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if database_links or tables:
|
||||||
|
databases.append(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"links": database_links,
|
||||||
|
"tables": tables,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return databases
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
return await self.render(["api_explorer.html"], request)
|
def api_path(link):
|
||||||
|
return "/-/api#{}".format(
|
||||||
|
urllib.parse.urlencode(
|
||||||
|
{
|
||||||
|
key: json.dumps(value, indent=2) if key == "json" else value
|
||||||
|
for key, value in link.items()
|
||||||
|
if key in ("path", "method", "json")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return await self.render(
|
||||||
|
["api_explorer.html"],
|
||||||
|
request,
|
||||||
|
{
|
||||||
|
"example_links": await self.example_links(request),
|
||||||
|
"api_path": api_path,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
Ładowanie…
Reference in New Issue