add fields to stats.

This commit is contained in:
Miroslav Šedivý 2021-11-17 22:10:55 +01:00
parent 6d7486ab57
commit f59128cf72
5 changed files with 70 additions and 18 deletions

View File

@ -7,6 +7,7 @@
- Ability to set locks from config `NEKO_LOCKS=control login`. - Ability to set locks from config `NEKO_LOCKS=control login`.
- Added control protection - users can gain control only if at least one admin is in the room `NEKO_CONTROL_PROTECTION=true`. - Added control protection - users can gain control only if at least one admin is in the room `NEKO_CONTROL_PROTECTION=true`.
- Emotes sending on mouse down holding. - Emotes sending on mouse down holding.
- Include `banned`, `locked`, `server_started_at`, `last_admin_left_at`, `last_user_left_at`, `control_protection` data in stats.
### Misc ### Misc
- ARM-based images not bound to Raspberry Pi only. - ARM-based images not bound to Raspberry Pi only.

View File

@ -1,11 +1,23 @@
package types package types
import "net/http" import (
"net/http"
"time"
)
type Stats struct { type Stats struct {
Connections uint32 `json:"connections"` Connections uint32 `json:"connections"`
Host string `json:"host"` Host string `json:"host"`
Members []*Member `json:"members"` Members []*Member `json:"members"`
Banned map[string]string `json:"banned"` // IP -> session ID (that banned it)
Locked map[string]string `json:"locked"` // resource name -> session ID (that locked it)
ServerStartedAt time.Time `json:"server_started_at"`
LastAdminLeftAt *time.Time `json:"last_admin_left_at"`
LastUserLeftAt *time.Time `json:"last_user_left_at"`
ControlProtection bool `json:"control_protection"`
} }
type WebSocket interface { type WebSocket interface {

View File

@ -292,8 +292,7 @@ func (h *MessageHandler) adminBan(id string, session types.Session, payload *mes
} }
h.logger.Debug().Str("address", remote).Msg("adding address to banned") h.logger.Debug().Str("address", remote).Msg("adding address to banned")
h.banned[address[0]] = id
h.banned[address[0]] = true
if err := target.Kick("banned"); err != nil { if err := target.Kick("banned"); err != nil {
return err return err

View File

@ -18,8 +18,8 @@ type MessageHandler struct {
webrtc types.WebRTCManager webrtc types.WebRTCManager
remote types.RemoteManager remote types.RemoteManager
broadcast types.BroadcastManager broadcast types.BroadcastManager
banned map[string]bool banned map[string]string // IP -> session ID (that banned it)
locked map[string]string locked map[string]string // resource name -> session ID (that locked it)
} }
func (h *MessageHandler) Connected(admin bool, socket *WebSocket) (bool, string) { func (h *MessageHandler) Connected(admin bool, socket *WebSocket) (bool, string) {
@ -27,8 +27,8 @@ func (h *MessageHandler) Connected(admin bool, socket *WebSocket) (bool, string)
if address == "" { if address == "" {
h.logger.Debug().Msg("no remote address") h.logger.Debug().Msg("no remote address")
} else { } else {
ok, banned := h.banned[address] _, ok := h.banned[address]
if ok && banned { if ok {
h.logger.Debug().Str("address", address).Msg("banned") h.logger.Debug().Str("address", address).Msg("banned")
return false, "banned" return false, "banned"
} }

View File

@ -18,12 +18,20 @@ import (
"m1k1o/neko/internal/utils" "m1k1o/neko/internal/utils"
) )
const CONTROL_PROTECTION_SESSION = "control_protection" const CONTROL_PROTECTION_SESSION = "by_control_protection"
func New(sessions types.SessionManager, remote types.RemoteManager, broadcast types.BroadcastManager, webrtc types.WebRTCManager, conf *config.WebSocket) *WebSocketHandler { func New(sessions types.SessionManager, remote types.RemoteManager, broadcast types.BroadcastManager, webrtc types.WebRTCManager, conf *config.WebSocket) *WebSocketHandler {
logger := log.With().Str("module", "websocket").Logger() logger := log.With().Str("module", "websocket").Logger()
locks := make(map[string]string) locks := make(map[string]string)
// if control protection is enabled
if conf.ControlProtection {
locks["control"] = CONTROL_PROTECTION_SESSION
logger.Info().Msgf("control locked on behalf of control protection")
}
// apply default locks
for _, lock := range conf.Locks { for _, lock := range conf.Locks {
locks[lock] = "" // empty session ID locks[lock] = "" // empty session ID
} }
@ -32,12 +40,6 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
logger.Info().Msgf("locked resources: %+v", conf.Locks) logger.Info().Msgf("locked resources: %+v", conf.Locks)
} }
// if control protection is enabled
if conf.ControlProtection {
locks["control"] = CONTROL_PROTECTION_SESSION
logger.Info().Msgf("control locked on behalf of control protection")
}
return &WebSocketHandler{ return &WebSocketHandler{
logger: logger, logger: logger,
shutdown: make(chan interface{}), shutdown: make(chan interface{}),
@ -55,10 +57,10 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
broadcast: broadcast, broadcast: broadcast,
sessions: sessions, sessions: sessions,
webrtc: webrtc, webrtc: webrtc,
banned: make(map[string]bool), banned: make(map[string]string),
locked: locks, locked: locks,
}, },
conns: 0, serverStartedAt: time.Now(),
} }
} }
@ -74,7 +76,12 @@ type WebSocketHandler struct {
remote types.RemoteManager remote types.RemoteManager
conf *config.WebSocket conf *config.WebSocket
handler *MessageHandler handler *MessageHandler
// stats
conns uint32 conns uint32
serverStartedAt time.Time
lastAdminLeftAt *time.Time
lastUserLeftAt *time.Time
} }
func (ws *WebSocketHandler) Start() { func (ws *WebSocketHandler) Start() {
@ -109,6 +116,13 @@ func (ws *WebSocketHandler) Start() {
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK) ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_UNLOCK)
} }
} }
// remove outdated stats
if session.Admin() {
ws.lastAdminLeftAt = nil
} else {
ws.lastUserLeftAt = nil
}
}) })
ws.sessions.OnDestroy(func(id string, session types.Session) { ws.sessions.OnDestroy(func(id string, session types.Session) {
@ -118,8 +132,13 @@ func (ws *WebSocketHandler) Start() {
ws.logger.Debug().Str("id", id).Msg("session destroyed") ws.logger.Debug().Str("id", id).Msg("session destroyed")
} }
membersCount := len(ws.sessions.Members())
adminCount := len(ws.sessions.Admins())
// if control protection is enabled and no admin // if control protection is enabled and no admin
if ws.conf.ControlProtection && len(ws.sessions.Admins()) == 0 { // and room is not locked, lock
_, ok := ws.handler.locked["control"]
if !ok && ws.conf.ControlProtection && adminCount == 0 {
ws.handler.locked["control"] = CONTROL_PROTECTION_SESSION ws.handler.locked["control"] = CONTROL_PROTECTION_SESSION
ws.logger.Info().Msgf("control locked and released on behalf of control protection") ws.logger.Info().Msgf("control locked and released on behalf of control protection")
ws.handler.adminRelease(id, session) ws.handler.adminRelease(id, session)
@ -133,6 +152,18 @@ func (ws *WebSocketHandler) Start() {
ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK) ws.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.ADMIN_LOCK)
} }
} }
// if this was the last admin
if session.Admin() && adminCount == 0 {
now := time.Now()
ws.lastAdminLeftAt = &now
}
// if this was the last user
if !session.Admin() && membersCount-adminCount == 0 {
now := time.Now()
ws.lastUserLeftAt = &now
}
}) })
ws.wg.Add(1) ws.wg.Add(1)
@ -274,6 +305,15 @@ func (ws *WebSocketHandler) Stats() types.Stats {
Connections: atomic.LoadUint32(&ws.conns), Connections: atomic.LoadUint32(&ws.conns),
Host: host, Host: host,
Members: ws.sessions.Members(), Members: ws.sessions.Members(),
Banned: ws.handler.banned,
Locked: ws.handler.locked,
ServerStartedAt: ws.serverStartedAt,
LastAdminLeftAt: ws.lastAdminLeftAt,
LastUserLeftAt: ws.lastUserLeftAt,
ControlProtection: ws.conf.ControlProtection,
} }
} }