mirror of
https://github.com/m1k1o/neko.git
synced 2025-05-02 20:05:54 +02:00
legacy: add stats.
This commit is contained in:
parent
6a8f8052cd
commit
a1f2e379cd
4 changed files with 158 additions and 55 deletions
|
@ -1,12 +1,17 @@
|
||||||
package legacy
|
package legacy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"m1k1o/neko/internal/api"
|
||||||
|
"m1k1o/neko/internal/api/room"
|
||||||
oldEvent "m1k1o/neko/internal/http/legacy/event"
|
oldEvent "m1k1o/neko/internal/http/legacy/event"
|
||||||
oldMessage "m1k1o/neko/internal/http/legacy/message"
|
oldMessage "m1k1o/neko/internal/http/legacy/message"
|
||||||
|
oldTypes "m1k1o/neko/internal/http/legacy/types"
|
||||||
|
|
||||||
"m1k1o/neko/pkg/types"
|
"m1k1o/neko/pkg/types"
|
||||||
"m1k1o/neko/pkg/types/event"
|
"m1k1o/neko/pkg/types/event"
|
||||||
|
@ -36,6 +41,7 @@ var (
|
||||||
type LegacyHandler struct {
|
type LegacyHandler struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
serverAddr string
|
serverAddr string
|
||||||
|
startedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *LegacyHandler {
|
func New() *LegacyHandler {
|
||||||
|
@ -44,6 +50,7 @@ func New() *LegacyHandler {
|
||||||
return &LegacyHandler{
|
return &LegacyHandler{
|
||||||
logger: log.With().Str("module", "legacy").Logger(),
|
logger: log.With().Str("module", "legacy").Logger(),
|
||||||
serverAddr: "127.0.0.1:8080",
|
serverAddr: "127.0.0.1:8080",
|
||||||
|
startedAt: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +71,7 @@ func (h *LegacyHandler) Route(r types.Router) {
|
||||||
// create a new session
|
// create a new session
|
||||||
username := r.URL.Query().Get("username")
|
username := r.URL.Query().Get("username")
|
||||||
password := r.URL.Query().Get("password")
|
password := r.URL.Query().Get("password")
|
||||||
token, err := s.create(username, password)
|
err = s.create(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error().Err(err).Msg("couldn't create a new session")
|
h.logger.Error().Err(err).Msg("couldn't create a new session")
|
||||||
|
|
||||||
|
@ -80,7 +87,7 @@ func (h *LegacyHandler) Route(r types.Router) {
|
||||||
defer s.destroy()
|
defer s.destroy()
|
||||||
|
|
||||||
// dial to the remote backend
|
// dial to the remote backend
|
||||||
connBackend, _, err := DefaultDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+token, nil)
|
connBackend, _, err := DefaultDialer.Dial("ws://"+h.serverAddr+"/api/ws?token="+s.token, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error().Err(err).Msg("couldn't dial to the remote backend")
|
h.logger.Error().Err(err).Msg("couldn't dial to the remote backend")
|
||||||
|
|
||||||
|
@ -174,24 +181,90 @@ func (h *LegacyHandler) Route(r types.Router) {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.Get("/stats", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
s := newSession(h.logger, h.serverAddr)
|
||||||
|
|
||||||
|
// create a new session
|
||||||
|
username := r.URL.Query().Get("usr")
|
||||||
|
password := r.URL.Query().Get("pwd")
|
||||||
|
err := s.create(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return utils.HttpForbidden(err.Error())
|
||||||
|
}
|
||||||
|
defer s.destroy()
|
||||||
|
|
||||||
|
if !s.isAdmin {
|
||||||
|
return utils.HttpUnauthorized().Msg("bad authorization")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// get all sessions
|
||||||
|
sessions := []api.SessionDataPayload{}
|
||||||
|
err = s.apiReq(http.MethodGet, "/api/sessions", nil, &sessions)
|
||||||
|
if err != nil {
|
||||||
|
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get current control status
|
||||||
|
control := room.ControlStatusPayload{}
|
||||||
|
err = s.apiReq(http.MethodGet, "/api/room/control", nil, &control)
|
||||||
|
if err != nil {
|
||||||
|
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get settings
|
||||||
|
settings := types.Settings{}
|
||||||
|
err = s.apiReq(http.MethodGet, "/api/room/settings", nil, &settings)
|
||||||
|
if err != nil {
|
||||||
|
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats oldTypes.Stats
|
||||||
|
|
||||||
|
// create empty array so that it's not null in json
|
||||||
|
stats.Members = []*oldTypes.Member{}
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.State.IsConnected {
|
||||||
|
stats.Connections++
|
||||||
|
member, err := profileToMember(session.ID, session.Profile)
|
||||||
|
if err != nil {
|
||||||
|
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||||
|
}
|
||||||
|
// append members
|
||||||
|
stats.Members = append(stats.Members, member)
|
||||||
|
} else if session.State.NotConnectedSince != nil {
|
||||||
|
//
|
||||||
|
// TODO: This wont work if the user is removed after the session is closed
|
||||||
|
//
|
||||||
|
// populate last admin left time
|
||||||
|
if session.Profile.IsAdmin && (stats.LastAdminLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastAdminLeftAt)) {
|
||||||
|
stats.LastAdminLeftAt = session.State.NotConnectedSince
|
||||||
|
}
|
||||||
|
// populate last user left time
|
||||||
|
if !session.Profile.IsAdmin && (stats.LastUserLeftAt == nil || (*session.State.NotConnectedSince).After(*stats.LastUserLeftAt)) {
|
||||||
|
stats.LastUserLeftAt = session.State.NotConnectedSince
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locks, err := s.settingsToLocks(settings)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.Host = control.HostId
|
||||||
|
// TODO: stats.Banned, not implemented yet
|
||||||
|
stats.Locked = locks
|
||||||
|
stats.ServerStartedAt = h.startedAt
|
||||||
|
stats.ControlProtection = settings.ControlProtection
|
||||||
|
stats.ImplicitControl = settings.ImplicitHosting
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(stats)
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
r.Get("/stats", func(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
password := r.URL.Query().Get("pwd")
|
|
||||||
isAdmin, err := webSocketHandler.IsAdmin(password)
|
|
||||||
if err != nil {
|
|
||||||
return utils.HttpForbidden(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isAdmin {
|
|
||||||
return utils.HttpUnauthorized().Msg("bad authorization")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
stats := webSocketHandler.Stats()
|
|
||||||
return json.NewEncoder(w).Encode(stats)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.Get("/screenshot.jpg", func(w http.ResponseWriter, r *http.Request) error {
|
r.Get("/screenshot.jpg", func(w http.ResponseWriter, r *http.Request) error {
|
||||||
password := r.URL.Query().Get("pwd")
|
password := r.URL.Query().Get("pwd")
|
||||||
isAdmin, err := webSocketHandler.IsAdmin(password)
|
isAdmin, err := webSocketHandler.IsAdmin(password)
|
||||||
|
|
|
@ -32,10 +32,11 @@ type session struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
serverAddr string
|
serverAddr string
|
||||||
|
|
||||||
id string
|
id string
|
||||||
token string
|
token string
|
||||||
name string
|
name string
|
||||||
client *http.Client
|
isAdmin bool
|
||||||
|
client *http.Client
|
||||||
|
|
||||||
lastHostID string
|
lastHostID string
|
||||||
lockedControls bool
|
lockedControls bool
|
||||||
|
@ -142,7 +143,7 @@ func (s *session) toBackend(event string, payload any) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) create(username, password string) (string, error) {
|
func (s *session) create(username, password string) error {
|
||||||
data := api.SessionDataPayload{}
|
data := api.SessionDataPayload{}
|
||||||
|
|
||||||
err := s.apiReq(http.MethodPost, "/api/login", api.SessionLoginPayload{
|
err := s.apiReq(http.MethodPost, "/api/login", api.SessionLoginPayload{
|
||||||
|
@ -150,19 +151,20 @@ func (s *session) create(username, password string) (string, error) {
|
||||||
Password: password,
|
Password: password,
|
||||||
}, &data)
|
}, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.id = data.ID
|
s.id = data.ID
|
||||||
s.token = data.Token
|
s.token = data.Token
|
||||||
s.name = data.Profile.Name
|
s.name = data.Profile.Name
|
||||||
|
s.isAdmin = data.Profile.IsAdmin
|
||||||
|
|
||||||
// if Cookie auth, the token will be empty
|
// if Cookie auth, the token will be empty
|
||||||
if s.token == "" {
|
if s.token == "" {
|
||||||
return "", fmt.Errorf("token not found - make sure you are not using Cookie auth on the server")
|
return fmt.Errorf("token not found - make sure you are not using Cookie auth on the server")
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Token, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) destroy() {
|
func (s *session) destroy() {
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Stats struct {
|
||||||
|
Connections uint32 `json:"connections"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
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"`
|
||||||
|
ImplicitControl bool `json:"implicit_control"`
|
||||||
|
}
|
||||||
|
|
||||||
type Member struct {
|
type Member struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"displayname"`
|
Name string `json:"displayname"`
|
||||||
|
|
|
@ -67,6 +67,41 @@ func screenConfigurations(screenSizes []types.ScreenSize) map[int]oldTypes.Scree
|
||||||
return screenSizesList
|
return screenSizesList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *session) settingsToLocks(settings types.Settings) (map[string]string, error) {
|
||||||
|
//
|
||||||
|
// FileTransfer
|
||||||
|
//
|
||||||
|
|
||||||
|
filetransferSettings := filetransfer.Settings{
|
||||||
|
Enabled: true, // defaults to true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := settings.Plugins.Unmarshal(filetransfer.PluginName, &filetransferSettings)
|
||||||
|
if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) {
|
||||||
|
return nil, fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", filetransfer.PluginName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Locks
|
||||||
|
//
|
||||||
|
|
||||||
|
locks := map[string]string{}
|
||||||
|
if settings.LockedLogins {
|
||||||
|
locks["login"] = "" // TODO: We don't know who locked the login.
|
||||||
|
s.lockedLogins = true
|
||||||
|
}
|
||||||
|
if settings.LockedControls {
|
||||||
|
locks["control"] = "" // TODO: We don't know who locked the control.
|
||||||
|
s.lockedControls = true
|
||||||
|
}
|
||||||
|
if !filetransferSettings.Enabled {
|
||||||
|
locks["file_transfer"] = "" // TODO: We don't know who locked the file transfer.
|
||||||
|
s.lockedFileTransfer = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return locks, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *session) sendControlHost(request message.ControlHost) error {
|
func (s *session) sendControlHost(request message.ControlHost) error {
|
||||||
lastHostID := s.lastHostID
|
lastHostID := s.lastHostID
|
||||||
|
|
||||||
|
@ -198,34 +233,9 @@ func (s *session) wsToClient(msg []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
locks, err := s.settingsToLocks(request.Settings)
|
||||||
// FileTransfer
|
if err != nil {
|
||||||
//
|
return err
|
||||||
|
|
||||||
filetransferSettings := filetransfer.Settings{
|
|
||||||
Enabled: true, // defaults to true
|
|
||||||
}
|
|
||||||
|
|
||||||
err = request.Settings.Plugins.Unmarshal(filetransfer.PluginName, &filetransferSettings)
|
|
||||||
if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) {
|
|
||||||
return fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", filetransfer.PluginName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Locks
|
|
||||||
//
|
|
||||||
locks := map[string]string{}
|
|
||||||
if request.Settings.LockedLogins {
|
|
||||||
locks["login"] = "" // TODO: We don't know who locked the login.
|
|
||||||
s.lockedLogins = true
|
|
||||||
}
|
|
||||||
if request.Settings.LockedControls {
|
|
||||||
locks["control"] = "" // TODO: We don't know who locked the control.
|
|
||||||
s.lockedControls = true
|
|
||||||
}
|
|
||||||
if !filetransferSettings.Enabled {
|
|
||||||
locks["file_transfer"] = "" // TODO: We don't know who locked the file transfer.
|
|
||||||
s.lockedFileTransfer = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.toClient(&oldMessage.SystemInit{
|
return s.toClient(&oldMessage.SystemInit{
|
||||||
|
|
Loading…
Add table
Reference in a new issue