282 lines
12 KiB
HTML
282 lines
12 KiB
HTML
{{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}}
|