Files
vibedns/web/templates/pages/settings_api.html
T
2026-08-16 21:18:45 -05:00

163 lines
7.0 KiB
HTML

{{define "content"}}
{{$csrf := .CSRF}}
<h1 class="page-title mb-1">Settings</h1>
<p class="text-body-secondary mb-4">Server configuration, stored in the database.</p>
{{template "settingsnav" .}}
<div class="row g-3">
<div class="col-12 col-xl-8">
<div class="card mb-3">
<div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
<span>API tokens</span>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#newTokenModal">
<i class="bi bi-plus-lg me-1"></i>Create token
</button>
</div>
{{if .Data.Tokens}}
<div class="table-responsive">
<table class="table table-hover align-middle table-compact">
<thead>
<tr>
<th>Name</th><th>Prefix</th><th>Created</th>
<th>Last used</th><th>Status</th><th class="row-actions">Actions</th>
</tr>
</thead>
<tbody>
{{range .Data.Tokens}}
<tr {{if not .Enabled}}class="is-disabled"{{end}}>
<td>
<span class="fw-semibold">{{.Name}}</span>
{{if .Description}}<div class="small text-body-secondary">{{truncate 70 .Description}}</div>{{end}}
</td>
<td class="mono small">vibedns_{{.Prefix}}…</td>
<td class="small text-body-secondary">{{timeAgo .CreatedAt}}</td>
<td class="small text-body-secondary">
{{if .LastUsedAt}}{{timeAgo .LastUsedAt}}{{else}}never{{end}}
</td>
<td><span class="badge {{badgeFor .Enabled}}">{{statusWord .Enabled}}</span></td>
<td class="row-actions">
<div class="btn-group">
{{template "postform" dict
"Action" (printf "/settings/api/tokens/%d/toggle" .ID) "CSRF" $csrf
"Fields" (dict "enabled" (boolstr (not .Enabled)))
"Icon" (toggleIcon .Enabled)
"Class" "btn btn-sm btn-outline-secondary"
"Title" (printf "%s this token" (toggleVerb .Enabled))}}
{{template "confirmform" dict
"Action" (printf "/settings/api/tokens/%d/delete" .ID) "CSRF" $csrf
"Icon" "bi-trash" "Title" "Revoke permanently"
"Message" (printf "Revoke the token %q? Any automation using it will stop working immediately." .Name)}}
</div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="card-body">
{{template "empty" dict "Icon" "bi-key" "Title" "No API tokens yet"
"Message" "A token lets a script use the REST API without the administrator password. Tokens are stored hashed, shown only once when created, and can be revoked individually."}}
<div class="text-center">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newTokenModal">
<i class="bi bi-plus-lg me-1"></i>Create the first token
</button>
</div>
</div>
{{end}}
</div>
<div class="card">
<div class="card-header">Using the API</div>
<div class="card-body">
<p class="text-body-secondary small">
Every resource lives under <span class="mono">/api/v1</span>. Authenticate with a
bearer token, or with the administrator's HTTP Basic credentials.
</p>
<div class="mb-3">
<div class="small fw-semibold mb-1">List zones</div>
<pre class="answer-block" id="exampleList">curl -H "Authorization: Bearer $VIBEDNS_TOKEN" \
{{default "http://127.0.0.1:8080" .Data.BaseURL}}/api/v1/zones</pre>
</div>
<div class="mb-3">
<div class="small fw-semibold mb-1">Add an A record</div>
<pre class="answer-block">curl -X POST -H "Authorization: Bearer $VIBEDNS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"www","type":"A","data":"192.0.2.10","ttl":3600}' \
{{default "http://127.0.0.1:8080" .Data.BaseURL}}/api/v1/zones/1/records</pre>
</div>
<div>
<div class="small fw-semibold mb-1">Import a blocklist</div>
<pre class="answer-block mb-0">curl -X POST -H "Authorization: Bearer $VIBEDNS_TOKEN" \
--data-binary @hosts.txt \
{{default "http://127.0.0.1:8080" .Data.BaseURL}}/api/v1/blacklists/1/import</pre>
</div>
</div>
</div>
</div>
<div class="col-12 col-xl-4">
<div class="card">
<div class="card-header">How tokens are stored</div>
<div class="card-body small text-body-secondary">
<p>
A token is 256 bits from the system random source. Only a short prefix — enough to
find the right row — and a SHA-256 hash are stored; the token itself is shown once,
at creation, and cannot be recovered afterwards.
</p>
<p>
A fast hash is appropriate here precisely because a token is not a human-chosen
password: there is no small guess space to search, and the hash is verified on
every API request.
</p>
<p class="mb-0">
Tokens carry the same authority as the administrator over the API, but cannot be
used to sign in to this interface or to change the administrator's credentials.
</p>
</div>
</div>
</div>
</div>
<div class="modal fade" id="newTokenModal" tabindex="-1" aria-hidden="true" aria-labelledby="newTokenModalLabel">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="/settings/api/tokens">
<input type="hidden" name="_csrf" value="{{$csrf}}">
<div class="modal-header">
<h5 class="modal-title" id="newTokenModalLabel">Create an API token</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label" for="tokenName">Name</label>
<input type="text" class="form-control" id="tokenName" name="name" required
placeholder="Ansible deployment">
</div>
<div class="mb-3">
<label class="form-label" for="tokenDescription">Description</label>
<textarea class="form-control" id="tokenDescription" name="description" rows="2"
placeholder="What uses this token, and from where"></textarea>
</div>
<div class="alert alert-warning small mb-0">
<i class="bi bi-exclamation-triangle me-1"></i>
The token is displayed once, immediately after creation. Copy it then — it cannot
be shown again.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create token</button>
</div>
</form>
</div>
</div>
</div>
{{end}}