initial commit

This commit is contained in:
2026-08-16 21:18:45 -05:00
commit 1e05a01bcf
122 changed files with 29178 additions and 0 deletions
+281
View File
@@ -0,0 +1,281 @@
{{define "content"}}
{{$l := .Data.List}}{{$csrf := .CSRF}}
{{$back := printf "/policies/lists/%d" $l.ID}}
{{$isBlack := eq $l.Kind "blacklist"}}
<nav aria-label="breadcrumb">
<ol class="breadcrumb small">
<li class="breadcrumb-item">
<a href="/policies/{{$l.Kind}}s">{{if $isBlack}}Blacklists{{else}}Allowlists{{end}}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{{$l.Name}}</li>
</ol>
</nav>
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-3">
<div>
<h1 class="page-title">{{$l.Name}}</h1>
{{if $l.Description}}<p class="text-body-secondary mb-0">{{$l.Description}}</p>{{end}}
</div>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#importListModal">
<i class="bi bi-upload me-1"></i>Import
</button>
<a href="/policies/lists/{{$l.ID}}/export" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-download me-1"></i>Export
</a>
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#addDomainModal">
<i class="bi bi-plus-lg me-1"></i>Add Domain
</button>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown"
aria-expanded="false"><i class="bi bi-three-dots"></i></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><button class="dropdown-item" type="button" data-bs-toggle="modal" data-bs-target="#editListModal">
<i class="bi bi-pencil me-2"></i>Edit list details</button></li>
<li><hr class="dropdown-divider"></li>
<li>
<div class="px-2">
{{template "confirmform" dict
"Action" (printf "/policies/lists/%d/clear" $l.ID) "CSRF" $csrf
"Label" "Remove all domains" "Icon" "bi-eraser"
"Class" "btn btn-sm btn-outline-danger w-100"
"Message" (printf "Remove all %d domains from %q? The list itself is kept." $l.DomainCount $l.Name)}}
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<dl class="list-meta mb-0">
<div>
<dt>Domains</dt>
<dd>{{num $l.DomainCount}}</dd>
</div>
<div>
<dt>Status</dt>
<dd><span class="badge {{badgeFor $l.Enabled}}">{{statusWord $l.Enabled}}</span></dd>
</div>
<div>
<dt>Updated</dt>
<dd class="fs-6 fw-normal">{{timeAgo $l.UpdatedAt}}</dd>
</div>
<div style="min-width:14rem;">
<dt>Used by</dt>
<dd class="fs-6 fw-normal">
{{if $l.UsedBy}}
<div class="d-flex flex-wrap gap-1">
{{range $l.UsedBy}}<span class="badge text-bg-light text-dark">{{.}}</span>{{end}}
</div>
{{else}}
<span class="text-body-secondary fst-italic">no policies</span>
{{end}}
</dd>
</div>
</dl>
</div>
</div>
{{if and (eq $l.DomainCount 0) (not .Data.Search)}}
{{template "empty" dict "Icon" (pick $isBlack "bi-shield-slash" "bi-shield-check") "Title" "This list is empty"
"Message" "Import a domain list to fill it. Plain lists, hosts files and Adblock-style rules are all understood, and a file with hundreds of thousands of lines is inserted in a single transaction."}}
<div class="text-center pb-4">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#importListModal">
<i class="bi bi-upload me-1"></i>Import domains
</button>
</div>
{{else}}
<div class="card">
<div class="card-body">
<form method="get" data-autosubmit class="row g-2 align-items-center mb-3">
<div class="col-12 col-md-6 col-lg-4">
<div class="input-group input-group-sm">
<span class="input-group-text"><i class="bi bi-search"></i></span>
<input type="search" class="form-control" name="q" value="{{.Data.Search}}"
placeholder="Search domains..." aria-label="Search domains">
</div>
</div>
<div class="col-auto">
{{if .Data.Search}}<a href="{{$back}}" class="btn btn-sm btn-link">Clear</a>{{end}}
</div>
</form>
{{if .Data.Entries}}
<div class="table-responsive">
<table class="table table-hover align-middle table-compact">
<thead>
<tr>
<th>Domain</th><th>Match</th><th>Status</th>
<th>Comment</th><th>Added</th><th class="row-actions">Actions</th>
</tr>
</thead>
<tbody>
{{range .Data.Entries}}
<tr {{if not .Enabled}}class="is-disabled"{{end}}>
<td class="mono">{{.Domain}}</td>
<td>
{{if .MatchSubdomains}}
<span class="badge text-bg-light text-dark" title="Also matches every subdomain">
<i class="bi bi-asterisk me-1"></i>+ subdomains
</span>
{{else}}
<span class="badge text-bg-light text-dark">exact only</span>
{{end}}
</td>
<td>
<span class="badge {{if $isBlack}}text-bg-danger{{else}}text-bg-success{{end}}">
{{if $isBlack}}Blocked{{else}}Allowed{{end}}
</span>
</td>
<td class="small text-body-secondary truncate-cell">{{.Comment}}</td>
<td class="small text-body-secondary">{{timeAgo .CreatedAt}}</td>
<td class="row-actions">
{{template "confirmform" dict
"Action" (printf "/policies/domains/%d/delete" .ID) "CSRF" $csrf
"ReturnTo" $back "Icon" "bi-trash"
"Message" (printf "Remove %s from %s?" .Domain $l.Name)}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{template "pagination" dict "P" .Data.Pagination "Q" .Query}}
{{else}}
{{template "empty" dict "Icon" "bi-search" "Title" "No domains match that search"
"Message" "Try a different term, or clear the search box."}}
{{end}}
</div>
</div>
{{end}}
{{/* ---- Import modal ---- */}}
<div class="modal fade" id="importListModal" tabindex="-1" aria-hidden="true" aria-labelledby="importListModalLabel">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" action="/policies/lists/{{$l.ID}}/import" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="{{$csrf}}">
<div class="modal-header">
<h5 class="modal-title" id="importListModalLabel">Import domains into {{$l.Name}}</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="listFile">Upload a file</label>
<input type="file" class="form-control" id="listFile" name="file" accept=".txt,.list,.hosts,text/plain">
<div class="form-text">
Plain lists, hosts files and Adblock-style rules are all accepted, mixed freely.
</div>
</div>
<div class="mb-3">
<label class="form-label" for="listPaste">…or paste domains</label>
<textarea class="form-control list-input" id="listPaste" name="content" rows="8"
placeholder="example.com
0.0.0.0 ads.example.net
||tracker.example.org^
# comments and blank lines are ignored"></textarea>
</div>
<div class="form-check form-switch">
<input type="hidden" name="match_subdomains" value="false">
<input class="form-check-input" type="checkbox" role="switch" id="matchSubs"
name="match_subdomains" value="true" checked>
<label class="form-check-label" for="matchSubs">
Also match subdomains
</label>
<div class="form-text">
With this on, blocking <span class="mono">example.com</span> also covers
<span class="mono">www.example.com</span> and <span class="mono">a.b.example.com</span>,
without storing them individually.
</div>
</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">Import</button>
</div>
</form>
</div>
</div>
</div>
{{/* ---- Add domain modal ---- */}}
<div class="modal fade" id="addDomainModal" tabindex="-1" aria-hidden="true" aria-labelledby="addDomainModalLabel">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="/policies/lists/{{$l.ID}}/domains">
<input type="hidden" name="_csrf" value="{{$csrf}}">
<input type="hidden" name="return_to" value="{{$back}}">
<div class="modal-header">
<h5 class="modal-title" id="addDomainModalLabel">Add a domain</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="domainInput">Domain</label>
<input type="text" class="form-control mono" id="domainInput" name="domain"
placeholder="example.com" required autocomplete="off">
<div class="form-text">A leading <span class="mono">*.</span> is accepted and turns on subdomain matching.</div>
</div>
<div class="mb-3">
<label class="form-label" for="domainComment">Comment</label>
<input type="text" class="form-control" id="domainComment" name="comment"
placeholder="Why this entry exists">
</div>
<div class="form-check form-switch">
<input type="hidden" name="match_subdomains" value="false">
<input class="form-check-input" type="checkbox" role="switch" id="domainSubs"
name="match_subdomains" value="true" checked>
<label class="form-check-label" for="domainSubs">Also match subdomains</label>
</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">Add domain</button>
</div>
</form>
</div>
</div>
</div>
{{/* ---- Edit list modal ---- */}}
<div class="modal fade" id="editListModal" tabindex="-1" aria-hidden="true" aria-labelledby="editListModalLabel">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="/policies/lists/{{$l.ID}}">
<input type="hidden" name="_csrf" value="{{$csrf}}">
<div class="modal-header">
<h5 class="modal-title" id="editListModalLabel">Edit {{$l.Name}}</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="editListName">Name</label>
<input type="text" class="form-control" id="editListName" name="name" value="{{$l.Name}}" required>
</div>
<div class="mb-3">
<label class="form-label" for="editListDescription">Description</label>
<textarea class="form-control" id="editListDescription" name="description" rows="2">{{$l.Description}}</textarea>
</div>
<div class="mb-3">
<label class="form-label" for="editListSource">Source URL</label>
<input type="url" class="form-control" id="editListSource" name="source_url" value="{{$l.SourceURL}}">
</div>
<div class="form-check form-switch">
<input type="hidden" name="enabled" value="false">
<input class="form-check-input" type="checkbox" role="switch" id="editListEnabled"
name="enabled" value="true" {{if $l.Enabled}}checked{{end}}>
<label class="form-check-label" for="editListEnabled">Enabled</label>
</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">Save</button>
</div>
</form>
</div>
</div>
</div>
{{end}}