more views things

Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
akp 2023-04-02 21:18:19 +01:00
parent b54400e1b3
commit b817d6a23f
No known key found for this signature in database
GPG key ID: AA5726202C8879B7
11 changed files with 171 additions and 24 deletions

View file

@ -3,8 +3,10 @@ package db
import (
"context"
"database/sql"
"fmt"
"github.com/codemicro/analytics/analytics/config"
"github.com/codemicro/analytics/analytics/db/migrations"
"github.com/codemicro/analytics/analytics/db/models"
"github.com/rs/zerolog/log"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/sqlitedialect"
@ -45,3 +47,21 @@ func New(conf *config.Config) (*DB, error) {
DB: db,
}, nil
}
func (db *DB) GetSessionsWithActivityAfter(minutes int, sort string) ([]*models.Session, error) {
var sessions []*models.Session
q := db.DB.NewSelect().
Model((*models.Session)(nil)).
ColumnExpr("*").
ColumnExpr(`(select max("time") as "time" from requests where session_id = "session"."id") as "last_seen"`)
if sort != "" {
q = q.Order(sort)
}
if minutes > 0 {
q = q.Where(fmt.Sprintf(`datetime() < datetime("last_seen", '+%d minutes')`, minutes))
}
if err := q.Scan(context.Background(), &sessions); err != nil {
return nil, err
}
return sessions, nil
}

View file

@ -16,7 +16,7 @@ func (i *Ingest) assignToSession(tx bun.Tx, request *models.Request) (*models.Se
Model(sess).
Where("ip_addr = ?", request.IPAddr).
Where("user_agent = ?", request.UserAgent).
Where(`? < datetime((select max("time") as "time" from requests where session_id = "session"."id"), '+30 minutes')`, request.Time).
Where(`? < datetime((select max("time") as "time" from requests where session_id = "session"."id"), '+2 hours')`, request.Time).
Scan(context.Background(), sess)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {

View file

@ -3,7 +3,6 @@ package webui
import (
"context"
"fmt"
"github.com/codemicro/analytics/analytics/db/models"
"github.com/flosch/pongo2/v6"
"github.com/gofiber/fiber/v2"
"strconv"
@ -23,16 +22,12 @@ func (wui *WebUI) partial_activeSessionsTable(ctx *fiber.Ctx) error {
{"Last seen", "last_seen", true, false},
},
Data: func(sortKey, sortDirection string) ([][]any, error) {
var sessions []*models.Session
q := wui.db.DB.NewSelect().
Model((*models.Session)(nil)).
ColumnExpr("*").
ColumnExpr(`(select max("time") as "time" from requests where session_id = "session"."id") as "last_seen"`).
Where(`datetime() < datetime("last_seen", '+30 minutes')`)
if sortKey != "" {
q = q.Order(sortKey + " " + sortDirection)
var sort string
if !(sortKey == "" || sortDirection == "") {
sort = sortKey + " " + sortDirection
}
if err := q.Scan(context.Background(), &sessions); err != nil {
sessions, err := wui.db.GetSessionsWithActivityAfter(30, sort)
if err != nil {
return nil, err
}
@ -64,6 +59,12 @@ func (wui *WebUI) partial_topURLs(ctx *fiber.Ctx) error {
n, _ = strconv.Atoi(nStr)
}
hoursStr := ctx.Query("hours")
var hours int
if hoursStr != "" {
hours, _ = strconv.Atoi(hoursStr)
}
ht := &HTMLTable{
Headers: []*HTMLTableHeader{
{"Count", "", false, false},
@ -77,10 +78,16 @@ func (wui *WebUI) partial_topURLs(ctx *fiber.Ctx) error {
Count int
}
q := wui.db.DB.NewSelect().
ColumnExpr(`"host", "uri", COUNT(*) as "count"`).Table("requests").GroupExpr(`"host", "uri"`).OrderExpr(`"count" DESC`)
ColumnExpr(`"host", "uri", COUNT(*) as "count"`).
Table("requests").
GroupExpr(`"host", "uri"`).
OrderExpr(`"count" DESC`)
if n > 0 {
q = q.Limit(n)
}
if hours > 0 {
q = q.Where(fmt.Sprintf(`datetime() < datetime(time, '+%d hours')`, hours))
}
if err := q.Scan(context.Background(), &counts); err != nil {
return nil, err
}

View file

@ -1,23 +1,23 @@
{% extends "extendable/base.html" %}
{% block main %}
<h2>Top 10 URLs</h2>
<h2>Top 10 URLs (past 24 hours)</h2>
<div hx-get="/partial/topURLs?n=10"
<div hx-get="/partial/topURLs?n=10&hours=24"
hx-trigger="load"
hx-swap="outerHTML"
>
Loading...
</div>
<a href="/top">[See all]</a>
<h2>Active sessions</h2>
<div hx-get="/partial/activeSessions"
<div hx-get="/partial/activeSessions?minutes=30"
hx-trigger="load"
hx-swap="outerHTML"
>
Loading...
</div>
<a href="/sessions">[See all]</a>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "extendable/base.html" %}
{% block main %}
<a href="/">[Home]</a>
<h2>All sessions</h2>
<div hx-get="/partial/listSessions"
hx-trigger="load"
hx-swap="outerHTML"
>
Loading...
</div>
{% endblock %}

View file

@ -1,7 +1,7 @@
{% extends "extendable/base.html" %}
{% block main %}
<a href="/">[< Back]</a>
<a href="/">[Home]</a>
<h2>Logs from session</h2>
@ -17,6 +17,4 @@
>
Loading...
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,20 @@
{% extends "extendable/base.html" %}
{% block main %}
<a href="/">[Home]</a>
<h2>Request detail</h2>
<p>
ID: {{ request.ID }}<br>
Time: {{ request.Time | date:"2006-01-02 15:04:05" }}<br>
IP address: {{ request.IPAddr }}<br>
Host: {{ request.Host }}<br>
Raw path: {{ request.RawURI }}<br>
Normalised path: {{ request.URI }}<br>
Referer: {% if request.Referer %}{{ request.Referer }}{% else %}<span class="italic">unset</span>{% endif %}<br>
User agent: {% if request.UserAgent %}{{ request.UserAgent }}{% else %}<span class="italic">unset</span>{% endif %}<br>
Status code: {{ request.StatusCode }}<br>
Session: <a href="/session?id={{ request.SessionID }}">{{ request.SessionID }}</a>
</p>
{% endblock %}

View file

@ -0,0 +1,51 @@
package webui
import (
"fmt"
"github.com/flosch/pongo2/v6"
"github.com/gofiber/fiber/v2"
)
func (wui *WebUI) page_listSessions(ctx *fiber.Ctx) error {
return wui.sendTemplate(ctx, "list-sessions.html", nil)
}
func (wui *WebUI) partial_listSessions(ctx *fiber.Ctx) error {
ht := &HTMLTable{
Path: "/partial/listSessions",
Headers: []*HTMLTableHeader{
{"", "", false, true},
{"User agent", "", false, false},
{"IP", "", false, false},
{"Last seen", "last_seen", true, false},
},
Data: func(sortKey, sortDirection string) ([][]any, error) {
var sort string
if !(sortKey == "" || sortDirection == "") {
sort = sortKey + " " + sortDirection
}
sessions, err := wui.db.GetSessionsWithActivityAfter(60*24, sort)
if err != nil {
return nil, err
}
var res [][]any
for _, sess := range sessions {
ua, _ := pongo2.ApplyFilter("truncatechars", pongo2.AsValue(sess.UserAgent), pongo2.AsValue(40))
ua, _ = pongo2.ApplyFilter("default", ua, unsetValue)
res = append(res, []any{
fmt.Sprintf(`<a href="/session?id=%s">[Link]</a>`, sess.ID),
ua,
getValue(pongo2.ApplyFilter("truncatechars", pongo2.AsValue(sess.IPAddr), pongo2.AsValue(30))),
getValue(pongo2.ApplyFilter("shortTimeSince", pongo2.AsValue(sess.LastSeen), nil)),
})
}
return res, nil
},
DefaultSortKey: "last_seen",
DefaultSortDirection: "desc",
ShowNumberOfEntries: true,
}
return wui.renderHTMLTable(ctx, ht)
}

View file

@ -0,0 +1,30 @@
package webui
import (
"context"
"database/sql"
"errors"
"github.com/codemicro/analytics/analytics/db/models"
"github.com/flosch/pongo2/v6"
"github.com/gofiber/fiber/v2"
)
func (wui *WebUI) page_requestDetail(ctx *fiber.Ctx) error {
id := ctx.Query("id")
if id == "" {
return fiber.ErrBadRequest
}
request := new(models.Request)
if err := wui.db.DB.NewSelect().Model(request).Where("id = ?", id).Scan(context.Background(), request); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fiber.ErrNotFound
}
return err
}
pctx := pongo2.Context{
"request": request,
}
return wui.sendTemplate(ctx, "request-detail.html", pctx)
}

View file

@ -14,10 +14,8 @@ import (
func (wui *WebUI) page_logsFromSession(ctx *fiber.Ctx) error {
id := ctx.Query("id")
pctx := make(pongo2.Context)
if id == "" {
return ctx.RedirectBack("/")
return fiber.ErrBadRequest
}
session := new(models.Session)
@ -27,7 +25,9 @@ func (wui *WebUI) page_logsFromSession(ctx *fiber.Ctx) error {
}
return err
}
pctx["session"] = session
pctx := pongo2.Context{
"session": session,
}
return wui.sendTemplate(ctx, "logs-from-session.html", pctx)
}
@ -44,6 +44,7 @@ func (wui *WebUI) partial_logsFromSession(ctx *fiber.Ctx) error {
{"Raw path", "raw_uri", true, false},
{"Status", "status_code", true, false},
{"Referer", "", false, false},
{"", "", false, true},
},
Data: func(sortKey, sortDirection string) ([][]any, error) {
var reqs []*models.Request
@ -63,6 +64,7 @@ func (wui *WebUI) partial_logsFromSession(ctx *fiber.Ctx) error {
request.RawURI,
request.StatusCode,
getValue(pongo2.ApplyFilter("default", pongo2.AsValue(request.Referer), unsetValue)),
fmt.Sprintf(`<a href="/request?id=%s">[Link]</a>`, request.ID),
})
}

View file

@ -104,6 +104,11 @@ func (wui *WebUI) registerHandlers() {
wui.app.Get("/partial/topURLs", wui.partial_topURLs)
wui.app.Get("/request", wui.page_requestDetail)
wui.app.Get("/sessions", wui.page_listSessions)
wui.app.Get("/partial/listSessions", wui.partial_listSessions)
wui.app.Use("/", filesystem.New(filesystem.Config{
Root: http.FS(static),
PathPrefix: "static",