web: add stats page

This commit is contained in:
Chris Sexton 2024-02-28 14:32:28 -05:00
parent 07470c5127
commit 25f9171223
7 changed files with 134 additions and 13 deletions

View File

@ -4,6 +4,7 @@ package bot
import ( import (
"fmt" "fmt"
"github.com/velour/catbase/bot/stats"
"github.com/velour/catbase/bot/web" "github.com/velour/catbase/bot/web"
"math/rand" "math/rand"
"os" "os"
@ -63,6 +64,7 @@ type bot struct {
quiet bool quiet bool
history *history.History history *history.History
stats *stats.Stats
} }
// Variable represents a $var replacement // Variable represents a $var replacement
@ -99,6 +101,7 @@ func New(config *config.Config, connector Connector) Bot {
filters: make(map[string]func(string) string), filters: make(map[string]func(string) string),
callbacks: make(CallbackMap), callbacks: make(CallbackMap),
history: history.New(historySz), history: history.New(historySz),
stats: stats.New(),
} }
bot.migrateDB() bot.migrateDB()
@ -106,7 +109,7 @@ func New(config *config.Config, connector Connector) Bot {
bot.RefreshPluginBlacklist() bot.RefreshPluginBlacklist()
bot.RefreshPluginWhitelist() bot.RefreshPluginWhitelist()
bot.web = web.New(bot.config) bot.web = web.New(bot.config, bot.stats)
connector.RegisterEvent(bot.Receive) connector.RegisterEvent(bot.Receive)

View File

@ -25,6 +25,8 @@ func (b *bot) Receive(conn Connector, kind Kind, msg msg.Message, args ...any) b
// msg := b.buildMessage(client, inMsg) // msg := b.buildMessage(client, inMsg)
// do need to look up user and fix it // do need to look up user and fix it
b.stats.MessagesRcv++
if kind == Edit { if kind == Edit {
b.history.Edit(msg.ID, &msg) b.history.Edit(msg.ID, &msg)
} else { } else {
@ -88,6 +90,7 @@ func (b *bot) Send(conn Connector, kind Kind, args ...any) (string, error) {
if b.quiet { if b.quiet {
return "", nil return "", nil
} }
b.stats.MessagesSent++
return conn.Send(kind, args...) return conn.Send(kind, args...)
} }

View File

@ -4,6 +4,7 @@ package bot
import ( import (
"fmt" "fmt"
"github.com/velour/catbase/bot/stats"
"github.com/velour/catbase/bot/web" "github.com/velour/catbase/bot/web"
"net/http" "net/http"
"regexp" "regexp"
@ -119,7 +120,7 @@ func NewMockBot() *MockBot {
Messages: make([]string, 0), Messages: make([]string, 0),
Actions: make([]string, 0), Actions: make([]string, 0),
} }
b.web = web.New(cfg) b.web = web.New(cfg, stats.New())
// If any plugin registered a route, we need to reset those before any new test // If any plugin registered a route, we need to reset those before any new test
http.DefaultServeMux = new(http.ServeMux) http.DefaultServeMux = new(http.ServeMux)
return &b return &b

19
bot/stats/stats.go Normal file
View File

@ -0,0 +1,19 @@
package stats
import (
"time"
)
type Stats struct {
MessagesSent int
MessagesRcv int
startTime time.Time
}
func New() *Stats {
return &Stats{startTime: time.Now()}
}
func (s Stats) Uptime() time.Duration {
return time.Now().Sub(s.startTime)
}

View File

@ -1,5 +1,7 @@
package web package web
import "fmt"
templ (w *Web) Header(title string) { templ (w *Web) Header(title string) {
<head> <head>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation.min.css" /> <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/foundation-sites@6.8.1/dist/css/foundation.min.css" />
@ -41,7 +43,7 @@ templ (w *Web) Nav(currentPage string) {
<div class="top-bar"> <div class="top-bar">
<div class="top-bar-left"> <div class="top-bar-left">
<ul class="menu"> <ul class="menu">
<li class="menu-text">{ w.botName() }</li> <li><a style="color: black; font-weight: bold;" href="/">{ w.botName() }</a></li>
for _, item := range w.GetWebNavigation() { for _, item := range w.GetWebNavigation() {
<li> <li>
if currentPage == item.Name { if currentPage == item.Name {
@ -55,3 +57,28 @@ templ (w *Web) Nav(currentPage string) {
</div> </div>
</div> </div>
} }
templ (w *Web) showStats() {
<div class="grid-container">
<div class="cell">
<h2>Stats</h2>
</div>
<div class="cell">
<table>
<tr>
<td>Messages Seen</td>
<td>{ fmt.Sprintf("%d", w.stats.MessagesRcv) }</td>
</tr>
<tr>
<td>Messages Sent</td>
<td>{ fmt.Sprintf("%d", w.stats.MessagesSent) }</td>
</tr>
<tr>
<td>Uptime</td>
<td>{ fmt.Sprintf("%v", w.stats.Uptime()) }</td>
</tr>
</table>
</div>
</div>
}

View File

@ -10,6 +10,8 @@ import "context"
import "io" import "io"
import "bytes" import "bytes"
import "fmt"
func (w *Web) Header(title string) templ.Component { func (w *Web) Header(title string) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
@ -35,7 +37,7 @@ func (w *Web) Header(title string) templ.Component {
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName()) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 8, Col: 32} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 10, Col: 32}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -48,7 +50,7 @@ func (w *Web) Header(title string) templ.Component {
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 8, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 10, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -66,7 +68,7 @@ func (w *Web) Header(title string) templ.Component {
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName()) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 10, Col: 32} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 12, Col: 32}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -175,20 +177,20 @@ func (w *Web) Nav(currentPage string) templ.Component {
templ_7745c5c3_Var7 = templ.NopComponent templ_7745c5c3_Var7 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"top-bar\"><div class=\"top-bar-left\"><ul class=\"menu\"><li class=\"menu-text\">") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"top-bar\"><div class=\"top-bar-left\"><ul class=\"menu\"><li><a style=\"color: black; font-weight: bold;\" href=\"/\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName()) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(w.botName())
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 43, Col: 51} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 45, Col: 86}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -214,7 +216,7 @@ func (w *Web) Nav(currentPage string) templ.Component {
var templ_7745c5c3_Var10 string var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 47, Col: 109} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 49, Col: 109}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -241,7 +243,7 @@ func (w *Web) Nav(currentPage string) templ.Component {
var templ_7745c5c3_Var12 string var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 49, Col: 71} return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 51, Col: 71}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -267,3 +269,66 @@ func (w *Web) Nav(currentPage string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
}) })
} }
func (w *Web) showStats() templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"grid-container\"><div class=\"cell\"><h2>Stats</h2></div><div class=\"cell\"><table><tr><td>Messages Seen</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", w.stats.MessagesRcv))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 70, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr><tr><td>Messages Sent</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", w.stats.MessagesSent))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 74, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr><tr><td>Uptime</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", w.stats.Uptime()))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `bot/web/index.templ`, Line: 78, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr></table></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}

View File

@ -6,6 +6,7 @@ import (
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httprate" "github.com/go-chi/httprate"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot/stats"
"github.com/velour/catbase/config" "github.com/velour/catbase/config"
"net/http" "net/http"
"strings" "strings"
@ -16,6 +17,7 @@ type Web struct {
config *config.Config config *config.Config
router *chi.Mux router *chi.Mux
httpEndPoints []EndPoint httpEndPoints []EndPoint
stats *stats.Stats
} }
type EndPoint struct { type EndPoint struct {
@ -40,7 +42,7 @@ func (ws *Web) GetWebNavigation() []EndPoint {
} }
func (ws *Web) serveRoot(w http.ResponseWriter, r *http.Request) { func (ws *Web) serveRoot(w http.ResponseWriter, r *http.Request) {
ws.Index("Home", nil).Render(r.Context(), w) ws.Index("Home", ws.showStats()).Render(r.Context(), w)
} }
func (ws *Web) serveNavHTML(w http.ResponseWriter, r *http.Request) { func (ws *Web) serveNavHTML(w http.ResponseWriter, r *http.Request) {
@ -95,10 +97,11 @@ func (ws *Web) ListenAndServe(addr string) {
log.Fatal().Err(http.ListenAndServe(addr, ws.router)).Msg("bot killed") log.Fatal().Err(http.ListenAndServe(addr, ws.router)).Msg("bot killed")
} }
func New(config *config.Config) *Web { func New(config *config.Config, s *stats.Stats) *Web {
w := &Web{ w := &Web{
config: config, config: config,
router: chi.NewRouter(), router: chi.NewRouter(),
stats: s,
} }
w.setupHTTP() w.setupHTTP()
return w return w