Веб-морда: поиск, фильтры, сортировка и пагинация клиентов; раскладка настроек
CI / test (push) Successful in 1m19s
Release / release (push) Successful in 1m18s

- список клиентов: поиск по домену и названию, фильтр по статусу (чипы
  со счётчиками), сортировка по любой колонке, пагинация 10/25/50/100;
  состояние в URL — отфильтрованный вид можно сохранить ссылкой
- размеры последних бэкапов одним запросом вместо запроса на клиента
- шапка выровнена по колонке контента; настройки на всю ширину:
  ntfy и Telegram двумя карточками, на узком экране — друг под другом

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-06 22:42:32 +03:00
1 parent c6942605c1
commit 83832fc734
8 files changed
+626 -179

No files matched your search

+41 -4
View File
@@ -1,15 +1,39 @@
{{define "title"}}Клиенты — backup-master{{end}}
{{define "content"}}
{{$v := .V}}{{$q := .V.Query}}
<div class="head">
<h1>Клиенты</h1>
<h1>Клиенты <span class="muted small">{{$v.Total}}</span></h1>
<a class="button primary" href="/clients/new">Добавить клиента</a>
</div>
{{if .Clients}}
{{if $v.Total}}
<form method="get" action="/" class="toolbar">
<input type="search" name="q" value="{{$q.Q}}" placeholder="Поиск по домену или названию" autofocus>
{{if $q.Status}}<input type="hidden" name="status" value="{{$q.Status}}">{{end}}
{{if ne $q.Sort "domain"}}<input type="hidden" name="sort" value="{{$q.Sort}}">{{end}}
{{with $q.DirParam}}<input type="hidden" name="dir" value="{{.}}">{{end}}
<select name="per" onchange="this.form.submit()" title="Строк на странице">
{{range $v.PerOpts}}<option value="{{.}}" {{if eq . $q.Per}}selected{{end}}>по {{.}}</option>{{end}}
</select>
<button>Найти</button>
{{if or $q.Q $q.Status}}<a class="button" href="/">Сбросить</a>{{end}}
</form>
<div class="chips toolbar">
{{range $v.Chips}}<a class="chip {{if .Active}}active{{end}}" href="{{.URL}}">{{.Title}} <span class="count">{{.Count}}</span></a>{{end}}
</div>
{{if $v.Rows}}
<div class="table-wrap">
<table>
<thead><tr><th>Домен</th><th>Название</th><th>Статус</th><th>Последний бэкап</th><th class="num">Размер</th><th>Агент на связи</th></tr></thead>
<thead><tr>
<th><a href="{{$q.SortURL "domain"}}" {{if eq $q.Sort "domain"}}class="sorted"{{end}}>Домен {{$q.SortMark "domain"}}</a></th>
<th><a href="{{$q.SortURL "name"}}" {{if eq $q.Sort "name"}}class="sorted"{{end}}>Название {{$q.SortMark "name"}}</a></th>
<th><a href="{{$q.SortURL "status"}}" {{if eq $q.Sort "status"}}class="sorted"{{end}}>Статус {{$q.SortMark "status"}}</a></th>
<th><a href="{{$q.SortURL "last_backup"}}" {{if eq $q.Sort "last_backup"}}class="sorted"{{end}}>Последний бэкап {{$q.SortMark "last_backup"}}</a></th>
<th class="num"><a href="{{$q.SortURL "size"}}" {{if eq $q.Sort "size"}}class="sorted"{{end}}>Размер {{$q.SortMark "size"}}</a></th>
<th><a href="{{$q.SortURL "last_seen"}}" {{if eq $q.Sort "last_seen"}}class="sorted"{{end}}>Агент на связи {{$q.SortMark "last_seen"}}</a></th>
</tr></thead>
<tbody>
{{range .Clients}}
{{range $v.Rows}}
<tr>
<td><a href="/clients/{{.ID}}">{{.Domain}}</a></td>
<td>{{if ne .Name .Domain}}{{.Name}}{{end}}</td>
@@ -22,6 +46,19 @@
</tbody>
</table>
</div>
<div class="pager">
<span>{{$v.From}}–{{$v.To}} из {{$v.Found}}{{if ne $v.Found $v.Total}} (всего {{$v.Total}}){{end}}</span>
{{if $v.Pages}}
<div class="pages">
{{if $v.Prev}}<a href="{{$v.Prev}}">←</a>{{end}}
{{range $v.Pages}}{{if .Gap}}<span class="gap">…</span>{{else if .Current}}<span class="current">{{.N}}</span>{{else}}<a href="{{.URL}}">{{.N}}</a>{{end}}{{end}}
{{if $v.Next}}<a href="{{$v.Next}}">→</a>{{end}}
</div>
{{end}}
</div>
{{else}}
<p class="empty">Ничего не найдено. <a href="/">Сбросить фильтры</a></p>
{{end}}
{{else}}
<p class="empty">Клиентов пока нет. Добавьте первый — получите команду установки агента.</p>
{{end}}