go-ssb-room/web/handlers/admin/dashboard.go

121 lines
3.1 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package admin
import (
"errors"
"fmt"
"net/http"
"time"
refs "github.com/ssbc/go-ssb-refs"
"go.mindeco.de/http/render"
"go.mindeco.de/log/level"
"go.mindeco.de/logging"
"github.com/ssbc/go-ssb-room/v2/internal/network"
"github.com/ssbc/go-ssb-room/v2/roomdb"
"github.com/ssbc/go-ssb-room/v2/roomstate"
weberrors "github.com/ssbc/go-ssb-room/v2/web/errors"
)
type dashboardHandler struct {
r *render.Renderer
flashes *weberrors.FlashHelper
roomState *roomstate.Manager
netInfo network.ServerEndpointDetails
dbs Databases
}
func (h dashboardHandler) overview(w http.ResponseWriter, req *http.Request) (interface{}, error) {
var (
err error
ctx = req.Context()
roomRef = h.netInfo.RoomID.String()
onlineRefs []refs.FeedRef
refsUpdateCh = make(chan []refs.FeedRef)
onlineCount = -1
)
// this is an attempt to sidestep the _dashboard doesn't render_ bug (issue #210)
// first we retreive the member state via a goroutine in the background
go func() {
refsUpdateCh <- h.roomState.ListAsRefs()
}()
// if it doesn't complete in 10 seconds the slice stays empty and onlineCount remains -1 (to indicate a problem)
select {
case <-time.After(10 * time.Second):
logger := logging.FromContext(ctx)
level.Warn(logger).Log("event", "didnt retreive room state in time")
case onlineRefs = <-refsUpdateCh:
onlineCount = len(onlineRefs)
}
// in the timeout case, nothing will happen here since the onlineRefs slice is empty
onlineUsers := make([]connectedUser, len(onlineRefs))
for i, ref := range onlineRefs {
// try to get the member
onlineUsers[i].Member, err = h.dbs.Members.GetByFeed(ctx, ref)
if err != nil {
if !errors.Is(err, roomdb.ErrNotFound) { // any other error can't be handled here
return nil, fmt.Errorf("failed to lookup online member: %w", err)
}
// if there is no member for this ref present it as role unknown
onlineUsers[i].ID = -1
onlineUsers[i].PubKey = ref
onlineUsers[i].Role = roomdb.RoleUnknown
}
}
memberCount, err := h.dbs.Members.Count(ctx)
if err != nil {
return nil, fmt.Errorf("failed to count members: %w", err)
}
inviteCount, err := h.dbs.Invites.Count(ctx, true)
if err != nil {
return nil, fmt.Errorf("failed to count active invites: %w", err)
}
deniedCount, err := h.dbs.DeniedKeys.Count(ctx)
if err != nil {
return nil, fmt.Errorf("failed to count denied keys: %w", err)
}
pageData := map[string]interface{}{
"RoomRef": roomRef,
"OnlineUsers": onlineUsers,
"OnlineCount": onlineCount,
"MemberCount": memberCount,
"InviteCount": inviteCount,
"DeniedCount": deniedCount,
}
pageData["Flashes"], err = h.flashes.GetAll(w, req)
if err != nil {
return nil, err
}
return pageData, nil
}
// connectedUser defines how we want to present a connected user
type connectedUser struct {
roomdb.Member
}
// if the member has an alias, use the first one. Otherwise use the public key
func (dm connectedUser) String() string {
if len(dm.Aliases) > 0 {
return dm.Aliases[0].Name
}
return dm.PubKey.String()
}