aliases: add overview and revoke handlers
This commit is contained in:
parent
9d60d09843
commit
50e4ebbaca
|
@ -245,6 +245,7 @@ func runroomsrv() error {
|
||||||
},
|
},
|
||||||
roomsrv.StateManager,
|
roomsrv.StateManager,
|
||||||
handlers.Databases{
|
handlers.Databases{
|
||||||
|
Aliases: db.Aliases,
|
||||||
AuthWithSSB: db.AuthWithSSB,
|
AuthWithSSB: db.AuthWithSSB,
|
||||||
AuthFallback: db.AuthFallback,
|
AuthFallback: db.AuthFallback,
|
||||||
AllowList: db.AllowList,
|
AllowList: db.AllowList,
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"go.mindeco.de/http/render"
|
||||||
|
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
|
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type aliasesHandler struct {
|
||||||
|
r *render.Renderer
|
||||||
|
|
||||||
|
db roomdb.AliasService
|
||||||
|
}
|
||||||
|
|
||||||
|
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")}
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := h.db.GetByID(req.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, roomdb.ErrNotFound) {
|
||||||
|
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
||||||
|
return nil, ErrRedirected
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"Entry": entry,
|
||||||
|
csrf.TemplateTag: csrf.TemplateField(req),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Method != "POST" {
|
||||||
|
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST request")}
|
||||||
|
h.r.Error(rw, req, http.StatusMethodNotAllowed, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := req.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||||
|
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := http.StatusFound
|
||||||
|
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||||
|
|
||||||
|
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status = http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(rw, req, redirectToAliases, status)
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAliasesOverview(t *testing.T) {
|
||||||
|
ts := newSession(t)
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
lst := []roomdb.Alias{
|
||||||
|
{ID: 1, Name: "alice", Feed: refs.FeedRef{ID: bytes.Repeat([]byte{0}, 32), Algo: "fake"}},
|
||||||
|
{ID: 2, Name: "bob", Feed: refs.FeedRef{ID: bytes.Repeat([]byte("1312"), 8), Algo: "test"}},
|
||||||
|
{ID: 3, Name: "cleo", Feed: refs.FeedRef{ID: bytes.Repeat([]byte("acab"), 8), Algo: "true"}},
|
||||||
|
}
|
||||||
|
ts.Aliases.ListReturns(lst, nil)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML("/aliases")
|
||||||
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
{"#welcome", "AdminAliasesWelcome"},
|
||||||
|
{"title", "AdminAliasesTitle"},
|
||||||
|
{"#aliasCount", "ListCountPlural"},
|
||||||
|
})
|
||||||
|
|
||||||
|
a.EqualValues(html.Find("#theList li").Length(), 3)
|
||||||
|
|
||||||
|
lst = []roomdb.Alias{
|
||||||
|
{ID: 666, Name: "dave", Feed: refs.FeedRef{ID: bytes.Repeat([]byte{1}, 32), Algo: "one"}},
|
||||||
|
}
|
||||||
|
ts.Aliases.ListReturns(lst, nil)
|
||||||
|
|
||||||
|
html, resp = ts.Client.GetHTML("/aliases")
|
||||||
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
{"#welcome", "AdminAliasesWelcome"},
|
||||||
|
{"title", "AdminAliasesTitle"},
|
||||||
|
{"#aliasCount", "ListCountSingular"},
|
||||||
|
})
|
||||||
|
|
||||||
|
elems := html.Find("#theList li")
|
||||||
|
a.EqualValues(elems.Length(), 1)
|
||||||
|
|
||||||
|
// check for link to Revoke confirm link
|
||||||
|
link, yes := elems.ContentsFiltered("a").Attr("href")
|
||||||
|
a.True(yes, "a-tag has href attribute")
|
||||||
|
a.Equal("/admin/aliases/revoke/confirm?id=666", link)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliasesRevokeConfirmation(t *testing.T) {
|
||||||
|
ts := newSession(t)
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
testKey, err := refs.ParseFeedRef("@x7iOLUcq3o+sjGeAnipvWeGzfuYgrXl8L4LYlxIhwDc=.ed25519")
|
||||||
|
a.NoError(err)
|
||||||
|
testEntry := roomdb.Alias{ID: 666, Name: "the-test-name", Feed: *testKey}
|
||||||
|
ts.Aliases.GetByIDReturns(testEntry, nil)
|
||||||
|
|
||||||
|
urlTo := web.NewURLTo(ts.Router)
|
||||||
|
urlRevokeConfirm := urlTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML(urlRevokeConfirm.String())
|
||||||
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
|
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||||
|
|
||||||
|
form := html.Find("form#confirm")
|
||||||
|
|
||||||
|
method, ok := form.Attr("method")
|
||||||
|
a.True(ok, "form has method set")
|
||||||
|
a.Equal("POST", method)
|
||||||
|
|
||||||
|
action, ok := form.Attr("action")
|
||||||
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
|
addURL, err := ts.Router.Get(router.AdminAliasesRevoke).URL()
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
a.Equal(addURL.String(), action)
|
||||||
|
|
||||||
|
webassert.InputsInForm(t, form, []webassert.InputElement{
|
||||||
|
{Name: "name", Type: "hidden", Value: testEntry.Name},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAliasesRevoke(t *testing.T) {
|
||||||
|
ts := newSession(t)
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
urlTo := web.NewURLTo(ts.Router)
|
||||||
|
urlRevoke := urlTo(router.AdminAliasesRevoke)
|
||||||
|
|
||||||
|
ts.Aliases.RevokeReturns(nil)
|
||||||
|
|
||||||
|
addVals := url.Values{"name": []string{"the-name"}}
|
||||||
|
rec := ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||||
|
a.Equal(http.StatusFound, rec.Code)
|
||||||
|
|
||||||
|
a.Equal(1, ts.Aliases.RevokeCallCount())
|
||||||
|
_, theName := ts.Aliases.RevokeArgsForCall(0)
|
||||||
|
a.EqualValues("the-name", theName)
|
||||||
|
|
||||||
|
// now for unknown ID
|
||||||
|
ts.Aliases.RevokeReturns(roomdb.ErrNotFound)
|
||||||
|
addVals = url.Values{"name": []string{"nope"}}
|
||||||
|
rec = ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||||
|
a.Equal(http.StatusNotFound, rec.Code)
|
||||||
|
//TODO: update redirect code with flash errors
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -27,6 +27,7 @@ type testSession struct {
|
||||||
Client *tester.Tester
|
Client *tester.Tester
|
||||||
Router *mux.Router
|
Router *mux.Router
|
||||||
|
|
||||||
|
Aliases *mockdb.FakeAliasService
|
||||||
AllowListDB *mockdb.FakeAllowListService
|
AllowListDB *mockdb.FakeAllowListService
|
||||||
PinnedDB *mockdb.FakePinnedNoticesService
|
PinnedDB *mockdb.FakePinnedNoticesService
|
||||||
NoticeDB *mockdb.FakeNoticesService
|
NoticeDB *mockdb.FakeNoticesService
|
||||||
|
@ -43,6 +44,7 @@ func newSession(t *testing.T) *testSession {
|
||||||
var ts testSession
|
var ts testSession
|
||||||
|
|
||||||
// fake dbs
|
// fake dbs
|
||||||
|
ts.Aliases = new(mockdb.FakeAliasService)
|
||||||
ts.AllowListDB = new(mockdb.FakeAllowListService)
|
ts.AllowListDB = new(mockdb.FakeAllowListService)
|
||||||
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
|
ts.PinnedDB = new(mockdb.FakePinnedNoticesService)
|
||||||
ts.NoticeDB = new(mockdb.FakeNoticesService)
|
ts.NoticeDB = new(mockdb.FakeNoticesService)
|
||||||
|
@ -97,6 +99,7 @@ func newSession(t *testing.T) *testSession {
|
||||||
r,
|
r,
|
||||||
ts.RoomState,
|
ts.RoomState,
|
||||||
Databases{
|
Databases{
|
||||||
|
Aliases: ts.Aliases,
|
||||||
AllowList: ts.AllowListDB,
|
AllowList: ts.AllowListDB,
|
||||||
Invites: ts.InvitesDB,
|
Invites: ts.InvitesDB,
|
||||||
Notices: ts.NoticeDB,
|
Notices: ts.NoticeDB,
|
||||||
|
|
|
@ -22,6 +22,9 @@ var HTMLTemplates = []string{
|
||||||
"admin/dashboard.tmpl",
|
"admin/dashboard.tmpl",
|
||||||
"admin/menu.tmpl",
|
"admin/menu.tmpl",
|
||||||
|
|
||||||
|
"admin/aliases.tmpl",
|
||||||
|
"admin/aliases-revoke-confirm.tmpl",
|
||||||
|
|
||||||
"admin/allow-list.tmpl",
|
"admin/allow-list.tmpl",
|
||||||
"admin/allow-list-remove-confirm.tmpl",
|
"admin/allow-list-remove-confirm.tmpl",
|
||||||
|
|
||||||
|
@ -34,6 +37,7 @@ var HTMLTemplates = []string{
|
||||||
|
|
||||||
// Databases is an option struct that encapsualtes the required database services
|
// Databases is an option struct that encapsualtes the required database services
|
||||||
type Databases struct {
|
type Databases struct {
|
||||||
|
Aliases roomdb.AliasService
|
||||||
AllowList roomdb.AllowListService
|
AllowList roomdb.AllowListService
|
||||||
Invites roomdb.InviteService
|
Invites roomdb.InviteService
|
||||||
Notices roomdb.NoticesService
|
Notices roomdb.NoticesService
|
||||||
|
@ -62,14 +66,22 @@ func Handler(
|
||||||
return map[string]interface{}{}, nil
|
return map[string]interface{}{}, nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
var ah = allowListHandler{
|
var ah = aliasesHandler{
|
||||||
|
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)
|
||||||
|
|
||||||
|
var mh = allowListHandler{
|
||||||
r: r,
|
r: r,
|
||||||
al: dbs.AllowList,
|
al: dbs.AllowList,
|
||||||
}
|
}
|
||||||
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", ah.overview))
|
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", mh.overview))
|
||||||
mux.HandleFunc("/members/add", ah.add)
|
mux.HandleFunc("/members/add", mh.add)
|
||||||
mux.HandleFunc("/members/remove/confirm", r.HTML("admin/allow-list-remove-confirm.tmpl", ah.removeConfirm))
|
mux.HandleFunc("/members/remove/confirm", r.HTML("admin/allow-list-remove-confirm.tmpl", mh.removeConfirm))
|
||||||
mux.HandleFunc("/members/remove", ah.remove)
|
mux.HandleFunc("/members/remove", mh.remove)
|
||||||
|
|
||||||
var ih = invitesHandler{
|
var ih = invitesHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
|
|
@ -19,8 +19,8 @@ import (
|
||||||
"go.mindeco.de/logging"
|
"go.mindeco.de/logging"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/admin"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/admin"
|
||||||
|
@ -42,6 +42,7 @@ var HTMLTemplates = []string{
|
||||||
|
|
||||||
// Databases is an options stuct for the required databases of the web handlers
|
// Databases is an options stuct for the required databases of the web handlers
|
||||||
type Databases struct {
|
type Databases struct {
|
||||||
|
Aliases roomdb.AliasService
|
||||||
AuthWithSSB roomdb.AuthWithSSBService
|
AuthWithSSB roomdb.AuthWithSSBService
|
||||||
AuthFallback roomdb.AuthFallbackService
|
AuthFallback roomdb.AuthFallbackService
|
||||||
AllowList roomdb.AllowListService
|
AllowList roomdb.AllowListService
|
||||||
|
@ -97,7 +98,11 @@ func New(
|
||||||
}),
|
}),
|
||||||
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
||||||
return func(routeName string) bool {
|
return func(routeName string) bool {
|
||||||
url, err := router.CompleteApp().Get(routeName).URLPath()
|
route := router.CompleteApp().Get(routeName)
|
||||||
|
if route == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
url, err := route.URLPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -226,6 +231,7 @@ func New(
|
||||||
r,
|
r,
|
||||||
roomState,
|
roomState,
|
||||||
admin.Databases{
|
admin.Databases{
|
||||||
|
Aliases: dbs.Aliases,
|
||||||
AllowList: dbs.AllowList,
|
AllowList: dbs.AllowList,
|
||||||
Invites: dbs.Invites,
|
Invites: dbs.Invites,
|
||||||
Notices: dbs.Notices,
|
Notices: dbs.Notices,
|
||||||
|
|
|
@ -17,6 +17,10 @@ AuthSignOut = "Sign out"
|
||||||
AdminDashboardWelcome = "Welcome to your dashboard"
|
AdminDashboardWelcome = "Welcome to your dashboard"
|
||||||
AdminDashboardTitle = "Room Admin Dashboard"
|
AdminDashboardTitle = "Room Admin Dashboard"
|
||||||
|
|
||||||
|
AdminAliasesTitle = "Aliases"
|
||||||
|
AdminAliasesWelcome = "Here you can see and revoke the registerd aliases of this room."
|
||||||
|
AdminAliasesRevoke = "Revoke"
|
||||||
|
|
||||||
AdminAllowListTitle = "Members"
|
AdminAllowListTitle = "Members"
|
||||||
AdminAllowListWelcome = "Here you can see all the members of the room and ways to add new ones (by their SSB ID) or remove exising ones."
|
AdminAllowListWelcome = "Here you can see all the members of the room and ways to add new ones (by their SSB ID) or remove exising ones."
|
||||||
AdminAllowListAdd = "Add"
|
AdminAllowListAdd = "Add"
|
||||||
|
|
|
@ -9,6 +9,10 @@ const (
|
||||||
AdminDashboard = "admin:dashboard"
|
AdminDashboard = "admin:dashboard"
|
||||||
AdminMenu = "admin:menu"
|
AdminMenu = "admin:menu"
|
||||||
|
|
||||||
|
AdminAliasesOverview = "admin:aliases:overview"
|
||||||
|
AdminAliasesRevokeConfirm = "admin:aliases:revoke:confirm"
|
||||||
|
AdminAliasesRevoke = "admin:aliases:revoke"
|
||||||
|
|
||||||
AdminAllowListOverview = "admin:allow-list:overview"
|
AdminAllowListOverview = "admin:allow-list:overview"
|
||||||
AdminAllowListAdd = "admin:allow-list:add"
|
AdminAllowListAdd = "admin:allow-list:add"
|
||||||
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm"
|
AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm"
|
||||||
|
@ -34,6 +38,10 @@ func Admin(m *mux.Router) *mux.Router {
|
||||||
m.Path("/dashboard").Methods("GET").Name(AdminDashboard)
|
m.Path("/dashboard").Methods("GET").Name(AdminDashboard)
|
||||||
m.Path("/menu").Methods("GET").Name(AdminMenu)
|
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)
|
||||||
|
|
||||||
m.Path("/members").Methods("GET").Name(AdminAllowListOverview)
|
m.Path("/members").Methods("GET").Name(AdminAllowListOverview)
|
||||||
m.Path("/members/add").Methods("POST").Name(AdminAllowListAdd)
|
m.Path("/members/add").Methods("POST").Name(AdminAllowListAdd)
|
||||||
m.Path("/members/remove/confirm").Methods("GET").Name(AdminAllowListRemoveConfirm)
|
m.Path("/members/remove/confirm").Methods("GET").Name(AdminAllowListRemoveConfirm)
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{{ define "title" }}{{i18n "AdminAliasesRevokeConfirmTitle"}}{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="flex flex-col justify-center items-center h-64">
|
||||||
|
|
||||||
|
<span
|
||||||
|
id="welcome"
|
||||||
|
class="text-center"
|
||||||
|
>{{i18n "AdminAliasesRevokeConfirmWelcome"}}</span>
|
||||||
|
|
||||||
|
<pre
|
||||||
|
id="verify"
|
||||||
|
class="my-4 font-mono truncate max-w-full text-lg text-gray-700"
|
||||||
|
>{{.Entry.Feed.Ref}}</pre>
|
||||||
|
|
||||||
|
<form id="confirm" action="{{urlTo "admin:aliases:revoke"}}" method="POST">
|
||||||
|
{{ .csrfField }}
|
||||||
|
<input type="hidden" name="name" value={{.Entry.Name}}>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<a
|
||||||
|
href="javascript:history.back()"
|
||||||
|
class="px-4 h-8 shadow rounded flex flex-row justify-center items-center bg-white align-middle text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-opacity-50"
|
||||||
|
>{{i18n "GenericGoBack"}}</a>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
>{{i18n "GenericConfirm"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -0,0 +1,69 @@
|
||||||
|
{{ 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"
|
||||||
|
>{{.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:allow-list: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:allow-list: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:allow-list: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}}
|
|
@ -18,6 +18,15 @@
|
||||||
</svg>{{i18n "NavAdminDashboard"}}
|
</svg>{{i18n "NavAdminDashboard"}}
|
||||||
</a>
|
</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
|
<a
|
||||||
href="{{urlTo "admin:allow-list:overview"}}"
|
href="{{urlTo "admin:allow-list:overview"}}"
|
||||||
class="{{if current_page_is "admin:allow-list: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"
|
class="{{if current_page_is "admin:allow-list: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"
|
||||||
|
|
Loading…
Reference in New Issue