163 lines
7.0 KiB
HTML
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}}
|