mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-04 11:03:52 +00:00
113 lines
5.6 KiB
HTML
113 lines
5.6 KiB
HTML
{{define "title"}}Repeaters · MeshTender{{end}}
|
|
{{define "header"}}
|
|
<div class="row g-2 align-items-center">
|
|
<div class="col">
|
|
<div class="page-pretitle">Your equipment</div>
|
|
<h1 class="page-title fs-1">Repeaters</h1>
|
|
</div>
|
|
<div class="col-auto ms-auto d-print-none">
|
|
<a class="btn btn-primary" href="/repeaters/add">{{template "icon-plus" "me-1"}}Add repeater</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{define "content"}}
|
|
{{if .Error}}<div class="alert alert-danger" role="alert">{{.Error}}</div>{{end}}
|
|
|
|
{{if .Repeaters}}
|
|
<div class="row row-cards" data-filter-root>
|
|
{{/* Search/filters come FIRST in the DOM and move to the right of the list at lg
|
|
(order-lg-last). Source order governs both the mobile stack and the tab order,
|
|
so ordering with CSS alone would leave keyboard and screen-reader users
|
|
traversing every row to reach the control that exists to avoid exactly that. */}}
|
|
<div class="col-lg-3 order-lg-last">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label" for="rep-search">Search</label>
|
|
<input type="search" id="rep-search" class="form-control" data-filter-search placeholder="Repeater name" aria-label="Search your repeaters">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label" for="rep-access">Access</label>
|
|
<select id="rep-access" class="form-select" data-filter-key="access" aria-label="Filter by access">
|
|
<option value="">All</option>
|
|
<option value="owned">Owned</option>
|
|
<option value="shared">Shared with me</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label" for="rep-status">Status</label>
|
|
<select id="rep-status" class="form-select" data-filter-key="status" aria-label="Filter by confirmation status">
|
|
<option value="">Any status</option>
|
|
<option value="confirmed">Confirmed</option>
|
|
<option value="unconfirmed">Unconfirmed</option>
|
|
</select>
|
|
</div>
|
|
<label class="form-check">
|
|
<input class="form-check-input" type="checkbox" data-filter-flag="sharedout">
|
|
<span class="form-check-label">Shared with others</span>
|
|
<span class="form-hint mt-0">Repeaters you own and have shared to at least one person or organization.</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- The repeater cards -->
|
|
<div class="col-lg-9">
|
|
<div class="row row-cards">
|
|
{{range .Repeaters}}
|
|
{{$shareCounts := index $.ShareCounts .ID}}
|
|
<div class="col-12" data-filter-item
|
|
data-name="{{.Name}}"
|
|
data-access="{{if .Shared}}shared{{else}}owned{{end}}"
|
|
data-status="{{if .Confirmed}}confirmed{{else}}unconfirmed{{end}}"
|
|
data-sharedout="{{if and (not .Shared) (or (gt $shareCounts.Users 0) (gt $shareCounts.Orgs 0))}}1{{else}}0{{end}}">
|
|
<a class="card card-link card-link-pop text-reset" href="/repeaters/{{.PublicID}}">
|
|
<div class="card-body py-3">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<span class="avatar bg-blue-lt d-none d-sm-flex">{{template "icon-antenna" ""}}</span>
|
|
<div class="flex-fill" style="min-width:0">
|
|
<div class="d-flex align-items-center flex-wrap gap-2">
|
|
<h2 class="m-0 fs-3">{{.Name}}</h2>
|
|
{{if .Shared}}<span class="badge bg-purple-lt">Shared with me</span>{{else}}<span class="badge bg-blue-lt">Owned</span>{{end}}
|
|
{{template "repstatus" .}}
|
|
</div>
|
|
<div class="d-flex align-items-center flex-wrap gap-2 mt-1 text-secondary">
|
|
<span class="font-monospace">{{slice .PublicKeyHex 0 8}}…</span>
|
|
<span class="ms-1">·</span>
|
|
{{if .Shared}}<span>Shared by {{.OwnerName}}</span>{{else}}<span>{{template "sharetext" $shareCounts}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{if and (not .Shared) .Confirmed .AccessKnown (not .IsAdmin)}}
|
|
<div class="alert alert-warning mt-3 mb-0">
|
|
MeshTender only has <strong>guest</strong> access here. Guest is available to anyone with a blank password, so there's no point operating at this level — re-run <code>setperm <your key> 3</code> on the repeater to grant admin, then re-test.
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
<div class="empty py-4" data-filter-empty hidden>
|
|
<p class="empty-title">No matches</p>
|
|
<p class="empty-subtitle text-secondary">No repeaters match your search and filters.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{else}}
|
|
<div class="empty">
|
|
<div class="empty-icon">{{template "icon-antenna" ""}}</div>
|
|
<p class="empty-title">No repeaters yet</p>
|
|
<p class="empty-subtitle text-secondary">Add one to grant MeshTender access and test it with a modem.</p>
|
|
<div class="empty-action">
|
|
<a href="/repeaters/add" class="btn btn-primary">{{template "icon-plus" "me-1"}}Add repeater</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* sharetext renders a repeater's sharing summary as plain text: e.g.
|
|
"Shared with 3 users, 2 organizations", omitting a zero count, or
|
|
"Not shared" when both are zero. Dot is a *store.ShareCount {Users, Orgs}. */}}
|
|
{{define "sharetext"}}{{if and (eq .Users 0) (eq .Orgs 0)}}Not shared{{else}}Shared with{{if gt .Users 0}} {{.Users}} user{{if ne .Users 1}}s{{end}}{{end}}{{if and (gt .Users 0) (gt .Orgs 0)}},{{end}}{{if gt .Orgs 0}} {{.Orgs}} organization{{if ne .Orgs 1}}s{{end}}{{end}}{{end}}{{end}}
|