Serves a page explaining why a domain was blocked instead of leaving a sinkholed client with a dead connection. Binds its own HTTP/HTTPS listeners with self-signed, per-hostname TLS certs generated on the fly, re-evaluates the requesting client against the policy engine per request, and renders an HTML template editable from Settings with a live preview. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTKpGMQzpfDsvedu1hvSUf
122 lines
5.4 KiB
HTML
122 lines
5.4 KiB
HTML
{{define "content"}}
|
|
{{$s := .Data.S}}
|
|
|
|
<h1 class="page-title mb-1">Settings</h1>
|
|
<p class="text-body-secondary mb-4">Server configuration, stored in the database.</p>
|
|
|
|
{{template "settingsnav" .}}
|
|
|
|
<form method="post" action="/settings/blockpage" data-preview-url="/settings/blockpage/preview">
|
|
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
|
|
|
<div class="row g-3">
|
|
<div class="col-12 col-xl-4">
|
|
<div class="card mb-3">
|
|
<div class="card-header">Block page server</div>
|
|
<div class="card-body">
|
|
<div class="form-check form-switch mb-3">
|
|
<input type="hidden" name="enabled" value="false">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="bpEnabled"
|
|
name="enabled" value="true" {{if $s.BlockPage.Enabled}}checked{{end}}>
|
|
<label class="form-check-label" for="bpEnabled">
|
|
Serve a block page on HTTP/HTTPS {{template "restartbadge"}}
|
|
</label>
|
|
</div>
|
|
<div class="form-text mb-3">
|
|
Point a policy's sinkhole address at this host, and a browser that
|
|
lands here instead of the real site sees the page below. Ports
|
|
below 1024 need root or
|
|
<span class="mono">CAP_NET_BIND_SERVICE</span>.
|
|
</div>
|
|
|
|
<label class="form-label" for="bpHTTP">HTTP listen address</label>
|
|
<input type="text" class="form-control mono mb-3" id="bpHTTP" name="http_listen"
|
|
value="{{$s.BlockPage.HTTPListen}}" placeholder=":80" required>
|
|
|
|
<label class="form-label" for="bpHTTPS">HTTPS listen address</label>
|
|
<input type="text" class="form-control mono mb-3" id="bpHTTPS" name="https_listen"
|
|
value="{{$s.BlockPage.HTTPSListen}}" placeholder=":443" required>
|
|
<div class="form-text">
|
|
HTTPS is served with a self-signed certificate generated per
|
|
hostname on the fly — browsers will show a trust warning, which
|
|
is expected: this is not the real site's certificate.
|
|
</div>
|
|
|
|
<hr class="my-3">
|
|
<div class="d-flex align-items-center gap-2 mb-2">
|
|
<span class="status-dot {{if .Data.Running}}status-up{{else}}status-down{{end}}"></span>
|
|
<span class="fw-semibold">
|
|
{{if .Data.Running}}Listeners running{{else}}Listeners stopped{{end}}
|
|
</span>
|
|
</div>
|
|
<dl class="row small mb-0">
|
|
<dt class="col-5 text-body-secondary">HTTP</dt>
|
|
<dd class="col-7 mono">{{if .Data.BoundHTTP}}{{.Data.BoundHTTP}}{{else}}—{{end}}</dd>
|
|
<dt class="col-5 text-body-secondary">HTTPS</dt>
|
|
<dd class="col-7 mono">{{if .Data.BoundHTTPS}}{{.Data.BoundHTTPS}}{{else}}—{{end}}</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">Available placeholders</div>
|
|
<div class="card-body">
|
|
<p class="text-body-secondary small mb-2">
|
|
Filled in per request by re-evaluating the requesting client
|
|
against the policy engine, the same way a DNS query would be.
|
|
</p>
|
|
<dl class="row small mb-0">
|
|
<dt class="col-5 mono">{{"{{.Domain}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Requested hostname</dd>
|
|
<dt class="col-5 mono">{{"{{.ListName}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Matched blocklist name</dd>
|
|
<dt class="col-5 mono">{{"{{.MatchedDomain}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Matched list entry</dd>
|
|
<dt class="col-5 mono">{{"{{.PolicyName}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Matched policy name</dd>
|
|
<dt class="col-5 mono">{{"{{.NetworkName}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Matched client network</dd>
|
|
<dt class="col-5 mono">{{"{{.ClientIP}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Requesting address</dd>
|
|
<dt class="col-5 mono">{{"{{.RequestPath}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">URL path requested</dd>
|
|
<dt class="col-5 mono">{{"{{.Scheme}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">http or https</dd>
|
|
<dt class="col-5 mono">{{"{{.Timestamp}}"}}</dt>
|
|
<dd class="col-7 text-body-secondary">Time of the request</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-xl-8">
|
|
<div class="card mb-3">
|
|
<div class="card-header">HTML editor</div>
|
|
<div class="card-body">
|
|
<textarea class="form-control mono" id="blockPageHTML" name="html" rows="22"
|
|
spellcheck="false" style="font-size:.85rem">{{$s.BlockPage.HTML}}</textarea>
|
|
<div class="form-text">
|
|
Standard Go template syntax. A broken template falls back to a
|
|
plain built-in page rather than breaking the listener.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">Preview</div>
|
|
<div class="card-body p-0">
|
|
<iframe id="blockPagePreview" title="Block page preview"
|
|
style="width:100%;height:480px;border:0;border-radius:0 0 .5rem .5rem;background:#fff"></iframe>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-actions mt-3">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-check-lg me-1"></i>Save block page settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{{end}}
|