combine aliases page with members page

This commit is contained in:
Andre Staltz 2021-04-07 14:38:03 +03:00
parent dafc341091
commit 4f70c62ab2
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
14 changed files with 249 additions and 243 deletions

View File

@ -20,6 +20,20 @@ type Members struct {
db *sql.DB
}
func getAliases(mEntry *models.Member) []roomdb.Alias {
if mEntry.R == nil || mEntry.R.Aliases == nil {
return make([]roomdb.Alias, 0)
}
var aliases = make([]roomdb.Alias, len(mEntry.R.Aliases))
for j, aEntry := range mEntry.R.Aliases {
aliases[j].ID = aEntry.ID
aliases[j].Feed = aEntry.R.Member.PubKey.FeedRef
aliases[j].Name = aEntry.Name
aliases[j].Signature = aEntry.Signature
}
return aliases
}
func (m Members) Add(ctx context.Context, pubKey refs.FeedRef, role roomdb.Role) (int64, error) {
var newID int64
err := transact(m.db, func(tx *sql.Tx) error {
@ -61,9 +75,10 @@ func (m Members) GetByID(ctx context.Context, mid int64) (roomdb.Member, error)
return roomdb.Member{}, err
}
return roomdb.Member{
ID: entry.ID,
Role: roomdb.Role(entry.Role),
PubKey: entry.PubKey.FeedRef,
ID: entry.ID,
Role: roomdb.Role(entry.Role),
PubKey: entry.PubKey.FeedRef,
Aliases: getAliases(entry),
}, nil
}
@ -82,16 +97,18 @@ func (m Members) GetByFeed(ctx context.Context, h refs.FeedRef) (roomdb.Member,
// List returns a list of all the feeds.
func (m Members) List(ctx context.Context) ([]roomdb.Member, error) {
all, err := models.Members().All(ctx, m.db)
all, err := models.Members(qm.Load("Aliases")).All(ctx, m.db)
if err != nil {
return nil, err
}
var members = make([]roomdb.Member, len(all))
for i, listEntry := range all {
members[i].ID = listEntry.ID
members[i].Role = roomdb.Role(listEntry.Role)
members[i].PubKey = listEntry.PubKey.FeedRef
for i, entry := range all {
members[i].ID = entry.ID
members[i].Role = roomdb.Role(entry.Role)
members[i].PubKey = entry.PubKey.FeedRef
members[i].Aliases = getAliases(entry)
println(len(members[i].Aliases))
}
return members, nil

View File

@ -28,9 +28,10 @@ type Alias struct {
// Member holds all the information an internal user of the room has.
type Member struct {
ID int64
Role Role
PubKey refs.FeedRef
ID int64
Role Role
PubKey refs.FeedRef
Aliases []Alias
}
//go:generate go run golang.org/x/tools/cmd/stringer -type=Role

View File

@ -25,25 +25,6 @@ type aliasesHandler struct {
const redirectToAliases = "/admin/aliases"
func (h aliasesHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
lst, err := h.db.List(req.Context())
if err != nil {
return nil, err
}
// Reverse the slice to provide recent-to-oldest results
for i, j := 0, len(lst)-1; i < j; i, j = i+1, j-1 {
lst[i], lst[j] = lst[j], lst[i]
}
pageData, err := paginate(lst, len(lst), req.URL.Query())
if err != nil {
return nil, err
}
return pageData, nil
}
func (h aliasesHandler) revokeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
if req.Method != "GET" {
return nil, weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected GET request")}

View File

@ -25,7 +25,6 @@ var HTMLTemplates = []string{
"admin/dashboard.tmpl",
"admin/menu.tmpl",
"admin/aliases.tmpl",
"admin/aliases-revoke-confirm.tmpl",
"admin/denied-keys.tmpl",
@ -37,7 +36,8 @@ var HTMLTemplates = []string{
"admin/notice-edit.tmpl",
"admin/members.tmpl",
"admin/member.tmpl",
"admin/member-list.tmpl",
"admin/members-remove-confirm.tmpl",
}
@ -93,7 +93,6 @@ func Handler(
r: r,
db: dbs.Aliases,
}
mux.HandleFunc("/aliases", r.HTML("admin/aliases.tmpl", ah.overview))
mux.HandleFunc("/aliases/revoke/confirm", r.HTML("admin/aliases-revoke-confirm.tmpl", ah.revokeConfirm))
mux.HandleFunc("/aliases/revoke", ah.revoke)
@ -110,7 +109,8 @@ func Handler(
r: r,
db: dbs.Members,
}
mux.HandleFunc("/members", r.HTML("admin/members.tmpl", mh.overview))
mux.HandleFunc("/member", r.HTML("admin/member.tmpl", mh.details))
mux.HandleFunc("/members", r.HTML("admin/member-list.tmpl", mh.overview))
mux.HandleFunc("/members/add", mh.add)
mux.HandleFunc("/members/change-role", mh.changeRole)
mux.HandleFunc("/members/remove/confirm", r.HTML("admin/members-remove-confirm.tmpl", mh.removeConfirm))

View File

@ -104,7 +104,8 @@ func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
return
}
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
memberDetailsURL := fmt.Sprint("/admin/member?id=", memberID)
http.Redirect(w, req, memberDetailsURL, http.StatusTemporaryRedirect)
}
func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
@ -129,6 +130,28 @@ func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (int
return pageData, nil
}
func (h membersHandler) details(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
if err != nil {
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
return nil, err
}
member, err := h.db.GetByID(req.Context(), id)
if err != nil {
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
return nil, err
}
roles := []roomdb.Role{roomdb.RoleMember, roomdb.RoleModerator, roomdb.RoleAdmin}
return map[string]interface{}{
"Member": member,
"AllRoles": roles,
csrf.TemplateTag: csrf.TemplateField(req),
}, nil
}
func (h membersHandler) removeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
if err != nil {

View File

@ -37,10 +37,6 @@ AuthFallbackInstruct = "This method is an acceptable fallback, if you have a use
AdminDashboardTitle = "Dashboard"
AdminDashboardWelcome = "Welcome to your dashboard"
AdminAliasesTitle = "Aliases"
AdminAliasesWelcome = "Here you can see and revoke the registered aliases of this room."
AdminAliasesRevoke = "Revoke"
AdminDeniedKeysTitle = "Banned"
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
AdminDeniedKeysAdd = "Add"
@ -53,9 +49,17 @@ AdminDeniedKeysRemoveConfirmTitle = "Confirm member removal"
AdminMembersTitle = "Members"
AdminMembersWelcome = "Here you can see all the members of the room and ways to add new ones (by their SSB ID) or remove exising ones."
AdminMembersAdd = "Add"
AdminMembersRemove = "Remove"
AdminMembersRemoveConfirmWelcome = "Are you sure you want to remove this member? They will lose their alias, if they have one."
AdminMembersRemoveConfirmTitle = "Confirm member removal"
AdminMembersRemoveConfirmWelcome = "Are you sure you want to remove this member? They will lose their alias, if they have one."
AdminMemberDetailsTitle = "Member details"
AdminMemberDetailsSSBID = "SSB Identifier"
AdminMemberDetailsRole = "Permission level"
AdminMemberDetailsAliases = "Aliases"
AdminMemberDetailsAliasRevoke = "Revoke"
AdminMemberDetailsExclusion = "Exclusion from this room"
AdminMemberDetailsRemove = "Remove member"
AdminInvitesTitle = "Invites"
AdminInvitesWelcome = "Create invite tokens for people who are not yet members of this room. At the same time as you create an invite, you can also (optionally) attach an alias to it so that the person who receives the invite can automatically use that claim that alias for themselves."
@ -69,7 +73,6 @@ AdminInviteRevoke = "Revoke"
AdminInviteRevokeConfirmTitle = "Confirm invite revocation"
AdminInviteRevokeConfirmWelcome = "Are you sure you want to remove this invite? If you already sent it out, they will not be able to use it."
# TODO: add placeholder support to the template helpers (https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60)
AdminInviteCreatedBy = "Created by:"
AdminInviteSuggestedAliasIs = "The suggested alias is:"

View File

@ -9,7 +9,6 @@ const (
AdminDashboard = "admin:dashboard"
AdminMenu = "admin:menu"
AdminAliasesOverview = "admin:aliases:overview"
AdminAliasesRevokeConfirm = "admin:aliases:revoke:confirm"
AdminAliasesRevoke = "admin:aliases:revoke"
@ -18,6 +17,8 @@ const (
AdminDeniedKeysRemoveConfirm = "admin:denied-keys:remove:confirm"
AdminDeniedKeysRemove = "admin:denied-keys:remove"
AdminMemberDetails = "admin:member:details"
AdminMembersOverview = "admin:members:overview"
AdminMembersAdd = "admin:members:add"
AdminMembersChangeRole = "admin:members:change-role"
@ -44,7 +45,6 @@ func Admin(m *mux.Router) *mux.Router {
m.Path("/dashboard").Methods("GET").Name(AdminDashboard)
m.Path("/menu").Methods("GET").Name(AdminMenu)
m.Path("/aliases").Methods("GET").Name(AdminAliasesOverview)
m.Path("/aliases/revoke/confirm").Methods("GET").Name(AdminAliasesRevokeConfirm)
m.Path("/aliases/revoke").Methods("POST").Name(AdminAliasesRevoke)
@ -53,6 +53,8 @@ func Admin(m *mux.Router) *mux.Router {
m.Path("/denied/remove/confirm").Methods("GET").Name(AdminDeniedKeysRemoveConfirm)
m.Path("/denied/remove").Methods("POST").Name(AdminDeniedKeysRemove)
m.Path("/member").Methods("GET").Name(AdminMemberDetails)
m.Path("/members").Methods("GET").Name(AdminMembersOverview)
m.Path("/members/add").Methods("POST").Name(AdminMembersAdd)
m.Path("/members/change-role").Methods("POST").Name(AdminMembersChangeRole)

View File

@ -5,7 +5,10 @@ module.exports = {
extend: {},
},
variants: {
extend: {},
extend: {
backgroundColor: ['odd'],
zIndex: ['hover'],
}
},
plugins: [],
};

View File

@ -1,72 +0,0 @@
{{ define "title" }}{{i18n "AdminAliasesTitle"}}{{ end }}
{{ define "content" }}
<h1
class="text-3xl tracking-tight font-black text-black mt-2 mb-4"
>{{i18n "AdminAliasesTitle"}}</h1>
<p id="welcome" class="my-2">{{i18n "AdminAliasesWelcome"}}</p>
<p
id="aliasCount"
class="text-lg font-bold my-2"
>{{i18npl "ListCount" .Count}}</p>
<ul id="theList" class="divide-y pb-4">
{{range .Entries}}
<li class="flex flex-row items-center h-12">
<span
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
>{{.Name}}</span>
<span
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
>{{.Feed.Ref}}</span>
<a
href="{{urlTo "admin:aliases:revoke:confirm" "id" .ID}}"
class="pl-4 w-20 py-2 text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
>{{i18n "AdminAliasesRevoke"}}</a>
</li>
{{end}}
</ul>
{{$pageNums := .Paginator.PageNums}}
{{$view := .View}}
{{if gt $pageNums 1}}
<div class="flex flex-row justify-center">
{{if not .FirstInView}}
<a
href="{{urlTo "admin:members:overview"}}?page=1"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>1</a>
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
{{end}}
{{range $view.Pages}}
{{if le . $pageNums}}
{{if eq . $view.Current}}
<span
class="px-3 py-2 cursor-default text-gray-500 border-2 border-transparent"
>{{.}}</span>
{{else}}
<a
href="{{urlTo "admin:members:overview"}}?page={{.}}"
class="rounded px-3 py-2 mx-1 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if not .LastInView}}
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
<a
href="{{urlTo "admin:members:overview"}}?page={{$view.Last}}"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{$view.Last}}</a>
{{end}}
</div>
{{end}}
{{end}}

View File

@ -0,0 +1,96 @@
{{ define "title" }}{{i18n "AdminMembersTitle"}}{{ end }}
{{ define "content" }}
<h1
class="text-3xl tracking-tight font-black text-black mt-2 mb-4"
>{{i18n "AdminMembersTitle"}}</h1>
<p id="welcome" class="my-2">{{i18n "AdminMembersWelcome"}}</p>
<form
id="add-entry"
action="{{urlTo "admin:members:add"}}"
method="POST"
>
{{ .csrfField }}
<label class="block mt-6 mb-1 text-sm text-gray-500">Add a new member</label>
<div class="flex flex-row items-center justify-start mb-6">
<input
type="text"
name="pub_key"
placeholder="{{i18n "PubKeyRefPlaceholder"}}"
class="w-8/12 self-stretch shadow rounded border border-transparent h-10 p-1 pl-4 font-mono truncate text-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent">
<button
type="submit"
class="ml-4 h-10 shadow rounded px-6 text-purple-600 ring-1 ring-purple-400 bg-white hover:bg-purple-600 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-purple-400"
>{{i18n "AdminMembersAdd"}}</button>
</div>
</form>
<p
id="membersCount"
class="text-lg font-bold my-2 ml-4"
>{{i18npl "MemberCount" .Count}}</p>
<ul id="theList" class="pb-4">
{{range $index, $member := .Entries}}
<li class="odd:bg-gray-100 rounded-lg relative z-0 hover:z-10 hover:bg-white hover:shadow-md">
<a href="{{urlTo "admin:member:details" "id" $member.ID}}" class="group flex flex-row items-center h-16 px-4 rounded-lg">
<div class="flex flex-col flex-1 justify-center max-w-full">
<span class="font-mono truncate text-gray-600 group-hover:text-gray-800">{{$member.PubKey.Ref}}</span>
<div class="inline-block h-6">
{{range $member.Aliases}}
<span class="mr-1 text-purple-800 bg-purple-100 rounded-lg px-2">{{.Name}}</span>
{{end}}
</div>
</div>
<svg class="w-6 h-6 text-gray-400 group-hover:text-purple-600" viewBox="0 0 24 24">
<path fill="currentColor" d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
</svg>
</a>
</li>
{{end}}
</ul>
{{$pageNums := .Paginator.PageNums}}
{{$view := .View}}
{{if gt $pageNums 1}}
<div class="flex flex-row justify-center">
{{if not .FirstInView}}
<a
href="{{urlTo "admin:members:overview"}}?page=1"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>1</a>
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
{{end}}
{{range $view.Pages}}
{{if le . $pageNums}}
{{if eq . $view.Current}}
<span
class="px-3 py-2 cursor-default text-gray-500 border-2 border-transparent"
>{{.}}</span>
{{else}}
<a
href="{{urlTo "admin:members:overview"}}?page={{.}}"
class="rounded px-3 py-2 mx-1 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if not .LastInView}}
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
<a
href="{{urlTo "admin:members:overview"}}?page={{$view.Last}}"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{$view.Last}}</a>
{{end}}
</div>
{{end}}
{{end}}

View File

@ -0,0 +1,77 @@
{{ define "title" }}{{i18n "AdminMemberDetailsTitle"}}{{ end }}
{{ define "content" }}
<h1
class="text-3xl tracking-tight font-black text-black mt-2 mb-4"
>{{i18n "AdminMemberDetailsTitle"}}</h1>
<label class="mt-2 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsSSBID"}}</label>
<p class="mb-8 font-mono font-bold tracking-wider truncate text-gray-900">{{.Member.PubKey.Ref}}</p>
<label class="mt-2 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsRole"}}</label>
<details class="mb-8 self-start w-40">
<summary class="px-3 py-1 rounded shadow bg-white ring-1 ring-gray-300 hover:bg-gray-100 cursor-pointer">
{{range $.AllRoles}}
{{if eq . $.Member.Role}}
{{i18n .String}}
{{end}}
{{end}}
</summary>
<div class="absolute z-10 bg-white w-40 mt-2 shadow-xl ring-1 ring-gray-200 rounded divide-y flex flex-col items-stretch overflow-hidden">
{{range $.AllRoles}}
{{if ne . $.Member.Role}}
<form
id="change-role"
action="{{urlTo "admin:members:change-role" "id" $.Member.ID}}"
method="POST"
>
{{$.csrfField}}
<input type="hidden" name="role" value="{{.}}">
<input
type="submit"
value="{{i18n .String}}"
class="pl-10 pr-3 py-2 w-full text-left bg-white text-gray-700 hover:text-gray-900 hover:bg-gray-50 cursor-pointer"
/>
</form>
{{else}}
<div
class="pr-3 py-2 text-gray-600 flex flex-row items-center cursor-default"
>
<div class="w-10 flex flex-row items-center justify-center">
<svg class="w-4 h-4" viewBox="0 0 24 24">
<path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
</svg>
</div>
<span>{{i18n .String}}</span>
</div>
{{end}}
{{end}}
</div>
</details>
{{$aliasCount := len .Member.Aliases}} {{if gt $aliasCount 0}}
<label class="mt-2 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsAliases"}}</label>
<div class="grid grid-cols-2 gap-y-2 gap-x-4 self-start">
{{range .Member.Aliases}}
<div class="flex flex-row items-center justify-start">
<a
href="/alias/{{.Name}}"
class="underline text-purple-800 bg-purple-100 rounded-lg px-2 py-1"
>{{.Name}}</a>
</div>
<a
href="{{urlTo "admin:aliases:revoke:confirm" "id" .ID}}"
class="w-20 py-2 text-sm text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
>({{i18n "AdminMemberDetailsAliasRevoke"}})</a>
{{end}}
</div>
{{end}}
<label class="mt-10 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsExclusion"}}</label>
<a
href="{{urlTo "admin:members:remove:confirm" "id" .Member.ID}}"
class="mb-8 self-start shadow rounded px-3 py-1 text-red-600 ring-1 ring-red-400 bg-white hover:bg-red-600 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-red-400 cursor-pointer"
>{{i18n "AdminMemberDetailsRemove"}}</a>
{{end}}

View File

@ -23,7 +23,7 @@
<button
type="submit"
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
class="shadow rounded px-4 h-8 text-gray-100 bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-opacity-50"
>{{i18n "GenericConfirm"}}</button>
</div>
</form>

View File

@ -1,115 +0,0 @@
{{ define "title" }}{{i18n "AdminMembersTitle"}}{{ end }}
{{ define "content" }}
<h1
class="text-3xl tracking-tight font-black text-black mt-2 mb-4"
>{{i18n "AdminMembersTitle"}}</h1>
<p id="welcome" class="my-2">{{i18n "AdminMembersWelcome"}}</p>
<p
id="membersCount"
class="text-lg font-bold my-2"
>{{i18npl "MemberCount" .Count}}</p>
<ul id="theList" class="divide-y pb-4">
<form
id="add-entry"
action="{{urlTo "admin:members:add"}}"
method="POST"
>
{{ .csrfField }}
<div class="flex flex-row items-center h-12">
<input
type="text"
name="pub_key"
placeholder="{{i18n "PubKeyRefPlaceholder"}}"
class="font-mono truncate w-1/2 ml-3 tracking-wider h-12 text-gray-900 focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent placeholder-gray-300"
>
<input
type="submit"
value="{{i18n "AdminMembersAdd"}}"
class="pl-4 w-20 py-2 text-center text-green-500 hover:text-green-600 font-bold bg-transparent cursor-pointer"
>
</div>
</form>
{{range $index, $member := .Entries}}
<li class="flex flex-row items-center h-12">
<form
class="change-member-role"
action="{{urlTo "admin:members:change-role" "id" $member.ID}}"
method="POST"
>
{{ $.csrfField }}
<select name="role">
{{range $.AllRoles}}
<option
value="{{.}}"
{{if eq . $member.Role}}
selected
{{else}}
class="changed"
{{end}}
>{{i18n .String}}</option>
{{end}}
</select>
<!-- TODO: I'd like to keep this button hidden until the option is changed.
butt i'm not sure that's possible without client side javascript..?
https://stackoverflow.com/questions/16015933/how-can-i-show-a-hidden-div-when-a-select-option-is-selected
-->
<input type="submit" value="{{i18n "GenericSave"}}">
</form>
<span
class="font-mono truncate flex-auto text-gray-600 text-xs"
>{{$member.PubKey.Ref}}</span>
<a
href="{{urlTo "admin:members:remove:confirm" "id" $member.ID}}"
class="pl-4 w-20 py-2 text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
>{{i18n "AdminMembersRemove"}}</a>
</li>
{{end}}
</ul>
{{$pageNums := .Paginator.PageNums}}
{{$view := .View}}
{{if gt $pageNums 1}}
<div class="flex flex-row justify-center">
{{if not .FirstInView}}
<a
href="{{urlTo "admin:members:overview"}}?page=1"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>1</a>
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
{{end}}
{{range $view.Pages}}
{{if le . $pageNums}}
{{if eq . $view.Current}}
<span
class="px-3 py-2 cursor-default text-gray-500 border-2 border-transparent"
>{{.}}</span>
{{else}}
<a
href="{{urlTo "admin:members:overview"}}?page={{.}}"
class="rounded px-3 py-2 mx-1 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{.}}</a>
{{end}}
{{end}}
{{end}}
{{if not .LastInView}}
<span
class="px-3 py-2 text-gray-400 border-2 border-transparent"
>..</span>
<a
href="{{urlTo "admin:members:overview"}}?page={{$view.Last}}"
class="rounded px-3 py-2 text-pink-600 border-transparent hover:border-pink-400 border-2"
>{{$view.Last}}</a>
{{end}}
</div>
{{end}}
{{end}}

View File

@ -19,15 +19,6 @@
</svg>{{i18n "NavAdminDashboard"}}
</a>
<a
href="{{urlTo "admin:aliases:overview"}}"
class="{{if current_page_is "admin:aliases:overview"}}bg-gray-300 {{else}}hover:bg-gray-200 {{end}}pr-1 pl-2 py-3 sm:py-1 rounded-md flex flex-row items-center font-semibold text-sm text-gray-700 hover:text-gray-800 truncate"
>
<svg class="text-green-600 w-4 h-4 mr-1" viewBox="0 0 24 24">
<path fill="currentColor" d="M23,12L20.56,9.22L20.9,5.54L17.29,4.72L15.4,1.54L12,3L8.6,1.54L6.71,4.72L3.1,5.53L3.44,9.21L1,12L3.44,14.78L3.1,18.47L6.71,19.29L8.6,22.47L12,21L15.4,22.46L17.29,19.28L20.9,18.46L20.56,14.78L23,12M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9L10,17Z" />
</svg>{{i18n "AdminAliasesTitle"}}
</a>
<a
href="{{urlTo "admin:members:overview"}}"
class="{{if current_page_is "admin:members:overview"}}bg-gray-300 {{else}}hover:bg-gray-200 {{end}}pr-1 pl-2 py-3 sm:py-1 rounded-md flex flex-row items-center font-semibold text-sm text-gray-700 hover:text-gray-800 truncate"
@ -37,7 +28,6 @@
</svg>{{i18n "AdminMembersTitle"}}
</a>
<a
href="{{urlTo "admin:invites:overview"}}"
class="{{if current_page_is "admin:invites:overview"}}bg-gray-300 {{else}}hover:bg-gray-200 {{end}}pr-1 pl-2 py-3 sm:py-1 rounded-md flex flex-row items-center font-semibold text-sm text-gray-700 hover:text-gray-800 truncate"