Files
2026-08-16 21:18:45 -05:00

114 lines
4.6 KiB
HTML

{{define "content"}}
{{$csrf := .CSRF}}
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<h1 class="page-title">Client Networks</h1>
<p class="text-body-secondary mb-0">
Subnets that DNS policies are applied to. A query is matched against the most
specific network containing the client's address.
</p>
</div>
<a href="/policies/networks/new" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg me-1"></i>Add Network
</a>
</div>
{{if not .Data.Policies}}
<div class="alert alert-info d-flex align-items-start gap-2">
<i class="bi bi-info-circle mt-1"></i>
<div>
There are no policies to assign yet. A network without a policy is matched but
filters nothing.
<a href="/policies/rules/new" class="alert-link">Create a policy first</a>.
</div>
</div>
{{end}}
<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 networks..." aria-label="Search networks">
</div>
</div>
</form>
{{if .Data.Networks}}
<div class="row g-3">
{{range .Data.Networks}}
<div class="col-12 col-xl-6">
<div class="card h-100 {{if not .Enabled}}opacity-75{{end}}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
<div>
<h2 class="h6 mb-1">
<a href="/policies/networks/{{.ID}}" class="text-decoration-none">{{.Name}}</a>
</h2>
<div class="mono text-body-secondary">{{.CIDR}}</div>
</div>
<span class="badge {{badgeFor .Enabled}}">{{statusWord .Enabled}}</span>
</div>
{{if .Description}}
<p class="small text-body-secondary mb-2">{{.Description}}</p>
{{end}}
<div class="mb-3">
<div class="small fw-semibold text-body-secondary mb-1">DNS Policies</div>
{{if .Policies}}
<div class="d-flex flex-wrap gap-1">
{{range .Policies}}
<span class="badge {{if .Enabled}}text-bg-primary{{else}}text-bg-secondary{{end}}">
<i class="bi bi-shield-check me-1"></i>{{.Name}}
<span class="opacity-75">· {{.BlockAction.Label}}</span>
</span>
{{end}}
</div>
{{else}}
<span class="text-body-secondary small fst-italic">
No policies assigned — queries from this network are not filtered.
</span>
{{end}}
</div>
<div class="d-flex gap-2">
<a href="/policies/networks/{{.ID}}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-sliders me-1"></i>Edit policies
</a>
{{template "postform" dict
"Action" (printf "/policies/networks/%d/toggle" .ID) "CSRF" $csrf
"ReturnTo" "/policies/networks"
"Fields" (dict "enabled" (boolstr (not .Enabled)))
"Icon" (toggleIcon .Enabled) "Label" (toggleVerb .Enabled)
"Class" "btn btn-sm btn-outline-secondary"}}
{{template "confirmform" dict
"Action" (printf "/policies/networks/%d/delete" .ID) "CSRF" $csrf
"Icon" "bi-trash" "Label" "Delete"
"Message" (printf "Delete the network %q (%s)? Clients in this range will stop matching any policy." .Name .CIDR)}}
</div>
</div>
</div>
</div>
{{end}}
</div>
{{else if .Data.Search}}
{{template "empty" dict "Icon" "bi-search" "Title" "No networks match that search"
"Message" "Try a different term, or clear the search box."}}
{{else}}
{{template "empty" dict "Icon" "bi-router" "Title" "No client networks yet"
"Message" "A client network binds a subnet to one or more policies. For example, a guest Wi-Fi range on 100.64.30.0/24 might use the adult content, gambling and malware blacklists, while a trusted LAN uses malware only."}}
<div class="text-center pb-3">
<a href="/policies/networks/new" class="btn btn-primary">
<i class="bi bi-plus-lg me-1"></i>Add the first network
</a>
</div>
{{end}}
</div>
</div>
{{end}}