dashboard: change user roles
* implement SetRole on sqlite * add dropdown form to members table * add http endpoint for processing * Add comment to denied keys overview * update ban remove confirm page
This commit is contained in:
parent
a0be3e998b
commit
4f723ab050
|
@ -56,7 +56,10 @@ type MembersService interface {
|
|||
// RemoveID removes the feed for the ID from the list.
|
||||
RemoveID(context.Context, int64) error
|
||||
|
||||
// SetRole
|
||||
// SetRole changes the role of the passed member id.
|
||||
// It will return an error if the member doesn't exist.
|
||||
// It should also return an error if call would remove the last admin,
|
||||
// since only admins can change roles doing so would leave the room in a crippled state.
|
||||
SetRole(context.Context, int64, Role) error
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,19 @@ type FakeMembersService struct {
|
|||
result1 int64
|
||||
result2 error
|
||||
}
|
||||
ChangeRoleStub func(context.Context, int64, roomdb.Role) error
|
||||
changeRoleMutex sync.RWMutex
|
||||
changeRoleArgsForCall []struct {
|
||||
arg1 context.Context
|
||||
arg2 int64
|
||||
arg3 roomdb.Role
|
||||
}
|
||||
changeRoleReturns struct {
|
||||
result1 error
|
||||
}
|
||||
changeRoleReturnsOnCall map[int]struct {
|
||||
result1 error
|
||||
}
|
||||
GetByFeedStub func(context.Context, refs.FeedRef) (roomdb.Member, error)
|
||||
getByFeedMutex sync.RWMutex
|
||||
getByFeedArgsForCall []struct {
|
||||
|
@ -175,6 +188,69 @@ func (fake *FakeMembersService) AddReturnsOnCall(i int, result1 int64, result2 e
|
|||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRole(arg1 context.Context, arg2 int64, arg3 roomdb.Role) error {
|
||||
fake.changeRoleMutex.Lock()
|
||||
ret, specificReturn := fake.changeRoleReturnsOnCall[len(fake.changeRoleArgsForCall)]
|
||||
fake.changeRoleArgsForCall = append(fake.changeRoleArgsForCall, struct {
|
||||
arg1 context.Context
|
||||
arg2 int64
|
||||
arg3 roomdb.Role
|
||||
}{arg1, arg2, arg3})
|
||||
stub := fake.ChangeRoleStub
|
||||
fakeReturns := fake.changeRoleReturns
|
||||
fake.recordInvocation("ChangeRole", []interface{}{arg1, arg2, arg3})
|
||||
fake.changeRoleMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1, arg2, arg3)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRoleCallCount() int {
|
||||
fake.changeRoleMutex.RLock()
|
||||
defer fake.changeRoleMutex.RUnlock()
|
||||
return len(fake.changeRoleArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRoleCalls(stub func(context.Context, int64, roomdb.Role) error) {
|
||||
fake.changeRoleMutex.Lock()
|
||||
defer fake.changeRoleMutex.Unlock()
|
||||
fake.ChangeRoleStub = stub
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRoleArgsForCall(i int) (context.Context, int64, roomdb.Role) {
|
||||
fake.changeRoleMutex.RLock()
|
||||
defer fake.changeRoleMutex.RUnlock()
|
||||
argsForCall := fake.changeRoleArgsForCall[i]
|
||||
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRoleReturns(result1 error) {
|
||||
fake.changeRoleMutex.Lock()
|
||||
defer fake.changeRoleMutex.Unlock()
|
||||
fake.ChangeRoleStub = nil
|
||||
fake.changeRoleReturns = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) ChangeRoleReturnsOnCall(i int, result1 error) {
|
||||
fake.changeRoleMutex.Lock()
|
||||
defer fake.changeRoleMutex.Unlock()
|
||||
fake.ChangeRoleStub = nil
|
||||
if fake.changeRoleReturnsOnCall == nil {
|
||||
fake.changeRoleReturnsOnCall = make(map[int]struct {
|
||||
result1 error
|
||||
})
|
||||
}
|
||||
fake.changeRoleReturnsOnCall[i] = struct {
|
||||
result1 error
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *FakeMembersService) GetByFeed(arg1 context.Context, arg2 refs.FeedRef) (roomdb.Member, error) {
|
||||
fake.getByFeedMutex.Lock()
|
||||
ret, specificReturn := fake.getByFeedReturnsOnCall[len(fake.getByFeedArgsForCall)]
|
||||
|
@ -561,6 +637,8 @@ func (fake *FakeMembersService) Invocations() map[string][][]interface{} {
|
|||
defer fake.invocationsMutex.RUnlock()
|
||||
fake.addMutex.RLock()
|
||||
defer fake.addMutex.RUnlock()
|
||||
fake.changeRoleMutex.RLock()
|
||||
defer fake.changeRoleMutex.RUnlock()
|
||||
fake.getByFeedMutex.RLock()
|
||||
defer fake.getByFeedMutex.RUnlock()
|
||||
fake.getByIDMutex.RLock()
|
||||
|
|
|
@ -36,7 +36,7 @@ func TestDeniedKeys(t *testing.T) {
|
|||
|
||||
// looks ok at least
|
||||
created := time.Now()
|
||||
time.Sleep(time.Second / 2)
|
||||
time.Sleep(time.Second)
|
||||
okFeed := refs.FeedRef{ID: bytes.Repeat([]byte("b44d"), 8), Algo: refs.RefAlgoFeedSSB1}
|
||||
err = db.DeniedKeys.Add(ctx, okFeed, "be gone")
|
||||
r.NoError(err)
|
||||
|
@ -53,7 +53,7 @@ func TestDeniedKeys(t *testing.T) {
|
|||
r.Len(lst, 1)
|
||||
r.Equal(okFeed.Ref(), lst[0].PubKey.Ref())
|
||||
r.Equal("be gone", lst[0].Comment)
|
||||
r.True(lst[0].CreatedAt.After(created))
|
||||
r.True(lst[0].CreatedAt.After(created), "not created after the sleep?")
|
||||
|
||||
yes := db.DeniedKeys.HasFeed(ctx, okFeed)
|
||||
r.True(yes)
|
||||
|
|
|
@ -143,5 +143,30 @@ func (m Members) SetRole(ctx context.Context, id int64, r roomdb.Role) error {
|
|||
return err
|
||||
}
|
||||
|
||||
panic("TODO")
|
||||
return transact(m.db, func(tx *sql.Tx) error {
|
||||
m, err := models.FindMember(ctx, tx, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return roomdb.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// find the number of other admins
|
||||
admins, err := models.Members(
|
||||
qm.Where("id != ?", id),
|
||||
qm.Where("role = ?", roomdb.RoleAdmin),
|
||||
).Count(ctx, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if admins < 1 {
|
||||
return fmt.Errorf("need at least one other admin")
|
||||
}
|
||||
|
||||
m.Role = int64(r)
|
||||
_, err = m.Update(ctx, tx, boil.Whitelist("role"))
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
|
|
@ -136,4 +136,88 @@ func TestMembersByID(t *testing.T) {
|
|||
|
||||
_, yes = db.Members.GetByID(ctx, lst[0].ID)
|
||||
r.Error(yes)
|
||||
|
||||
r.NoError(db.Close())
|
||||
}
|
||||
|
||||
func TestMembersSetRole(t *testing.T) {
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
testRepo := filepath.Join("testrun", t.Name())
|
||||
os.RemoveAll(testRepo)
|
||||
|
||||
tr := repo.New(testRepo)
|
||||
|
||||
db, err := Open(tr)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create two users
|
||||
feedA := refs.FeedRef{ID: bytes.Repeat([]byte("1"), 32), Algo: refs.RefAlgoFeedSSB1}
|
||||
idA, err := db.Members.Add(ctx, "user-a", feedA, roomdb.RoleAdmin)
|
||||
r.NoError(err)
|
||||
t.Log("member A:", idA)
|
||||
|
||||
feedB := refs.FeedRef{ID: bytes.Repeat([]byte("2"), 32), Algo: refs.RefAlgoFeedSSB1}
|
||||
idB, err := db.Members.Add(ctx, "user-b", feedB, roomdb.RoleModerator)
|
||||
r.NoError(err)
|
||||
t.Log("member B:", idB)
|
||||
|
||||
// list and check
|
||||
members, err := db.Members.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(members, 2)
|
||||
findMemberWithRole(t, members, idA, roomdb.RoleAdmin)
|
||||
findMemberWithRole(t, members, idB, roomdb.RoleModerator)
|
||||
|
||||
// upgrade B to admin
|
||||
err = db.Members.SetRole(ctx, idB, roomdb.RoleAdmin)
|
||||
r.NoError(err)
|
||||
|
||||
// list and check
|
||||
members, err = db.Members.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(members, 2)
|
||||
findMemberWithRole(t, members, idA, roomdb.RoleAdmin)
|
||||
findMemberWithRole(t, members, idB, roomdb.RoleAdmin)
|
||||
|
||||
// downgrade A to member
|
||||
err = db.Members.SetRole(ctx, idA, roomdb.RoleMember)
|
||||
r.NoError(err)
|
||||
|
||||
// list and check
|
||||
members, err = db.Members.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(members, 2)
|
||||
findMemberWithRole(t, members, idA, roomdb.RoleMember)
|
||||
findMemberWithRole(t, members, idB, roomdb.RoleAdmin)
|
||||
|
||||
// can't downgrade B to member (need one admin)
|
||||
err = db.Members.SetRole(ctx, idB, roomdb.RoleMember)
|
||||
r.Error(err)
|
||||
|
||||
// unchanged
|
||||
members, err = db.Members.List(ctx)
|
||||
r.NoError(err)
|
||||
r.Len(members, 2)
|
||||
findMemberWithRole(t, members, idA, roomdb.RoleMember)
|
||||
findMemberWithRole(t, members, idB, roomdb.RoleAdmin)
|
||||
|
||||
r.NoError(db.Close())
|
||||
}
|
||||
|
||||
func findMemberWithRole(t *testing.T, members []roomdb.Member, id int64, r roomdb.Role) {
|
||||
var found = false
|
||||
|
||||
for _, m := range members {
|
||||
if m.ID == id {
|
||||
if m.Role != r {
|
||||
t.Errorf("memberd %d has the wrong role (has %s)", m.ID, m.Role)
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("memberd %d not in the list", id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,26 @@ const (
|
|||
RoleAdmin
|
||||
)
|
||||
|
||||
func (r *Role) UnmarshalText(text []byte) error {
|
||||
roleStr := string(text)
|
||||
switch roleStr {
|
||||
|
||||
case RoleAdmin.String():
|
||||
*r = RoleAdmin
|
||||
|
||||
case RoleModerator.String():
|
||||
*r = RoleModerator
|
||||
|
||||
case RoleMember.String():
|
||||
*r = RoleMember
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown member role: %q", roleStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ErrAlreadyAdded struct {
|
||||
Ref refs.FeedRef
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ func Handler(
|
|||
}
|
||||
mux.HandleFunc("/members", r.HTML("admin/members.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))
|
||||
mux.HandleFunc("/members/remove", mh.remove)
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/gorilla/csrf"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/user"
|
||||
)
|
||||
|
||||
type membersHandler struct {
|
||||
|
@ -70,6 +71,49 @@ func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
|||
http.Redirect(w, req, redirectToMembers, http.StatusFound)
|
||||
}
|
||||
|
||||
func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request"))
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := user.FromContext(req.Context())
|
||||
if currentUser == nil || currentUser.Role != roomdb.RoleAdmin {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusForbidden, fmt.Errorf("not an admin"))
|
||||
return
|
||||
}
|
||||
|
||||
memberID, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad member id: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
var role roomdb.Role
|
||||
if err := role.UnmarshalText([]byte(req.Form.Get("role"))); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.SetRole(req.Context(), memberID, role); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusInternalServerError, fmt.Errorf("failed to change member role: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
lst, err := h.db.List(req.Context())
|
||||
if err != nil {
|
||||
|
@ -87,6 +131,8 @@ func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
|||
|
||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||
|
||||
pageData["AllRoles"] = []roomdb.Role{roomdb.RoleMember, roomdb.RoleModerator, roomdb.RoleAdmin}
|
||||
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ GenericLanguage = "Language"
|
|||
|
||||
PageNotFound = "The requested page was not found."
|
||||
|
||||
RoleMember = "Member"
|
||||
RoleModerator = "Moderator"
|
||||
RoleAdmin = "Admin"
|
||||
|
||||
LandingTitle = "ohai my room"
|
||||
LandingWelcome = "Landing welcome here"
|
||||
|
||||
|
@ -22,11 +26,13 @@ AdminAliasesTitle = "Aliases"
|
|||
AdminAliasesWelcome = "Here you can see and revoke the registered aliases of this room."
|
||||
AdminAliasesRevoke = "Revoke"
|
||||
|
||||
AdminDeniedKeysTitle = "Denied Public Keys"
|
||||
AdminDeniedKeysWelcome = "This page can be used to ban/block SSB IDs so that they can't access the room any more."
|
||||
AdminDeniedKeysTitle = "Banned"
|
||||
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
|
||||
AdminDeniedKeysAdd = "Add"
|
||||
AdminDeniedKeysRemove = "Remove"
|
||||
AdminDeniedKeysRemoveConfirmWelcome = "Are you sure you want to remove this member? They will lose their alias, if they have one."
|
||||
AdminDeniedKeysComment = "Comment"
|
||||
AdminDeniedKeysCommentDescription = "The person who added this ban, added the following comment"
|
||||
AdminDeniedKeysRemoveConfirmWelcome = "Are you sure you want to remove this ban? They will will be able to access the room again."
|
||||
AdminDeniedKeysRemoveConfirmTitle = "Confirm member removal"
|
||||
|
||||
AdminMembersTitle = "Members"
|
||||
|
|
|
@ -20,6 +20,7 @@ const (
|
|||
|
||||
AdminMembersOverview = "admin:members:overview"
|
||||
AdminMembersAdd = "admin:members:add"
|
||||
AdminMembersChangeRole = "admin:members:change-role"
|
||||
AdminMembersRemoveConfirm = "admin:members:remove:confirm"
|
||||
AdminMembersRemove = "admin:members:remove"
|
||||
|
||||
|
@ -54,6 +55,7 @@ func Admin(m *mux.Router) *mux.Router {
|
|||
|
||||
m.Path("/members").Methods("GET").Name(AdminMembersOverview)
|
||||
m.Path("/members/add").Methods("POST").Name(AdminMembersAdd)
|
||||
m.Path("/members/change-role").Methods("POST").Name(AdminMembersChangeRole)
|
||||
m.Path("/members/remove/confirm").Methods("GET").Name(AdminMembersRemoveConfirm)
|
||||
m.Path("/members/remove").Methods("POST").Name(AdminMembersRemove)
|
||||
|
||||
|
|
|
@ -12,6 +12,17 @@
|
|||
class="my-4 font-mono truncate max-w-full text-lg text-gray-700"
|
||||
>{{.Entry.PubKey.Ref}}</pre>
|
||||
|
||||
<div class="has-tooltip">
|
||||
{{human_time .Entry.CreatedAt}}
|
||||
<span class="tooltip">{{.Entry.CreatedAt.Format "2006-01-02T15:04:05.00"}}</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
id="welcome"
|
||||
class="text-center"
|
||||
>{{i18n "AdminDeniedKeysCommentDescription"}}</span>
|
||||
<p>{{.Entry.Comment}}</p>
|
||||
|
||||
<form id="confirm" action="{{urlTo "admin:denied-keys:remove"}}" method="POST">
|
||||
{{ .csrfField }}
|
||||
<input type="hidden" name="id" value={{.Entry.ID}}>
|
||||
|
|
|
@ -23,13 +23,13 @@
|
|||
type="text"
|
||||
name="pub_key"
|
||||
placeholder="@ .ed25519"
|
||||
class="font-mono truncate flex-auto tracking-wider h-12 text-gray-900 focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent placeholder-gray-300"
|
||||
class="font-mono truncate w-1/2 mr-2 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="text"
|
||||
name="comment"
|
||||
placeholder="some comment"
|
||||
class="font-mono truncate flex-auto tracking-wider h-12 text-gray-900 focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent placeholder-gray-300"
|
||||
placeholder="{{i18n "AdminDeniedKeysComment"}}"
|
||||
class="font-mono truncate w-1/2 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"
|
||||
|
@ -41,9 +41,13 @@
|
|||
{{range .Entries}}
|
||||
<li class="flex flex-row items-center h-12">
|
||||
<span
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider text-xs"
|
||||
>{{.PubKey.Ref}}</span>
|
||||
|
||||
<span
|
||||
class="font-mono flex-auto text-gray-600 tracking-wider"
|
||||
>{{.Comment}}</span>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:denied-keys:remove:confirm" "id" .ID}}"
|
||||
class="pl-4 w-20 py-2 text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
|
||||
|
|
|
@ -23,13 +23,13 @@
|
|||
type="text"
|
||||
name="nick"
|
||||
placeholder="member nickname"
|
||||
class="font-mono truncate flex-auto tracking-wider h-12 text-gray-900 focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent placeholder-gray-300"
|
||||
class="font-mono truncate w-1/2 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="text"
|
||||
name="pub_key"
|
||||
placeholder="@ .ed25519"
|
||||
class="font-mono truncate flex-auto tracking-wider h-12 text-gray-900 focus:outline-none focus:ring-1 focus:ring-green-500 focus:border-transparent placeholder-gray-300"
|
||||
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"
|
||||
|
@ -38,14 +38,43 @@
|
|||
>
|
||||
</div>
|
||||
</form>
|
||||
{{range .Entries}}
|
||||
{{range $index, $member := .Entries}}
|
||||
<li class="flex flex-row items-center h-12">
|
||||
<span
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider"
|
||||
>{{.PubKey.Ref}}</span>
|
||||
class="font-mono truncate flex-auto text-gray-600 tracking-wider w-1/3"
|
||||
>{{$member.Nickname}}</span>
|
||||
|
||||
<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" .ID}}"
|
||||
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>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{{ define "menu" }}
|
||||
<!-- Icons are taken from https://materialdesignicons.com/ -->
|
||||
<div class="flex flex-col my-4 w-40 px-2 sm:pl-0">
|
||||
<a
|
||||
href="{{urlTo "complete:index"}}"
|
||||
|
@ -36,14 +37,6 @@
|
|||
</svg>{{i18n "AdminMembersTitle"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:denied-keys:overview"}}"
|
||||
class="{{if current_page_is "admin:denied-keys: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-purple-600 w-4 h-4 mr-1" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="" />
|
||||
</svg>{{i18n "AdminDeniedKeysTitle"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:invites:overview"}}"
|
||||
|
@ -54,6 +47,15 @@
|
|||
</svg>{{i18n "NavAdminInvites"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "admin:denied-keys:overview"}}"
|
||||
class="{{if current_page_is "admin:denied-keys: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-red-600 w-4 h-4 mr-1" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,0A12,12 0 0,1 24,12A12,12 0 0,1 12,24A12,12 0 0,1 0,12A12,12 0 0,1 12,0M12,2A10,10 0 0,0 2,12C2,14.4 2.85,16.6 4.26,18.33L18.33,4.26C16.6,2.85 14.4,2 12,2M12,22A10,10 0 0,0 22,12C22,9.6 21.15,7.4 19.74,5.67L5.67,19.74C7.4,21.15 9.6,22 12,22Z" />
|
||||
</svg>{{i18n "AdminDeniedKeysTitle"}}
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="{{urlTo "complete:notice:list"}}"
|
||||
class="{{if current_page_is "complete:notice:list"}}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