many things
Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
93a374d8f2
commit
91d16e7339
17 changed files with 588 additions and 6 deletions
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/sqlitedialect"
|
||||
"github.com/uptrace/bun/driver/sqliteshim"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
|
@ -23,6 +24,9 @@ func New(conf *config.Config) (*DB, error) {
|
|||
}
|
||||
|
||||
db := bun.NewDB(sqldb, sqlitedialect.New())
|
||||
if config.Debug {
|
||||
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
}
|
||||
|
||||
log.Info().Msg("migrating database")
|
||||
mig := migrate.NewMigrator(db, migrations.Migrations)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/uptrace/bun"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
|
@ -10,4 +12,10 @@ type Session struct {
|
|||
ID string `bun:",pk"`
|
||||
UserAgent string `bun:"type:VARCHAR COLLATE NOCASE"`
|
||||
IPAddr string
|
||||
|
||||
LastSeen time.Time `bun:",scanonly"`
|
||||
}
|
||||
|
||||
func (s *Session) String() string {
|
||||
return fmt.Sprintf("ID:%s UA:%#v IP:%s LastSeen:%s", s.ID, s.UserAgent, s.IPAddr, s.LastSeen.Format(time.DateTime))
|
||||
}
|
||||
|
|
57
analytics/webui/index.go
Normal file
57
analytics/webui/index.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/codemicro/analytics/analytics/db/models"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func (wui *WebUI) page_index(ctx *fiber.Ctx) error {
|
||||
return wui.sendTemplate(ctx, "index.html", nil)
|
||||
}
|
||||
|
||||
func (wui *WebUI) partial_activeSessionsTable(ctx *fiber.Ctx) error {
|
||||
ht := &HTMLTable{
|
||||
Path: "/partial/activeSessions",
|
||||
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 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)
|
||||
}
|
||||
if err := q.Scan(context.Background(), &sessions); 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)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<p>{{ sessions|length }} active session{% if sessions|length != 0%}s{% endif %}</p>
|
||||
{% if sessions|length != 0 %}
|
||||
<div class="table">
|
||||
<table>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>User agent</th>
|
||||
<th>IP</th>
|
||||
<th>Last seen</th>
|
||||
</tr>
|
||||
{% for session in sessions %}
|
||||
<tr>
|
||||
<td><a href="/session?id={{ session.ID }}">[Link]</a></td>
|
||||
<td title="{{ session.UserAgent }}">{{ session.UserAgent | truncatechars:40 }}</td>
|
||||
<td title="{{ session.IPAddr }}">{{ session.IPAddr | truncatechars:30 }}</td>
|
||||
<td>{{ session.LastSeen | shortTimeSince }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
31
analytics/webui/internal/templates/components/table.html
Normal file
31
analytics/webui/internal/templates/components/table.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div class="table" hx-target="this">
|
||||
<table>
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<th>
|
||||
{% if header.Sortable %}
|
||||
<a href="#" hx-get="{{ path }}?sortKey={{ header.Slug }}{% if sortKey and header.Slug == sortKey %}&sortDir={% if sortDirection == "asc" %}desc{% elif sortDirection == "desc" %}asc{% endif %}{% endif %}">
|
||||
{% endif %}
|
||||
{{ header.Name }}{% if sortKey %}{% if header.Slug == sortKey %} {% if sortDirection == "asc" %}▲{% else %}▼{% endif %}{% endif %}{% endif %}
|
||||
{% if header.Sortable %}</a>{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for row in rows %}
|
||||
<tr>
|
||||
{% for item in row %}
|
||||
{% with headers[forloop.Counter - 1] as header %}
|
||||
{% if header.Safe %}
|
||||
<td>{{ item | safe }}</td>
|
||||
{% else %}
|
||||
<td>{{ item }}</td>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if showNumberOfEntries %}
|
||||
<span style="margin-top: 5px" class="italic">{{ rows | length }} records</span>
|
||||
{% endif %}
|
||||
</div>
|
17
analytics/webui/internal/templates/extendable/base.html
Normal file
17
analytics/webui/internal/templates/extendable/base.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
<script src="https://unpkg.com/htmx.org@1.8.6"></script>
|
||||
{% block head %}{% endblock%}
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Analytics</h1>
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
14
analytics/webui/internal/templates/index.html
Normal file
14
analytics/webui/internal/templates/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends "extendable/base.html" %}
|
||||
|
||||
{% block main %}
|
||||
<h2>Active sessions</h2>
|
||||
|
||||
<div hx-get="/partial/activeSessions"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
31
analytics/webui/internal/templates/loader.go
Normal file
31
analytics/webui/internal/templates/loader.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"io"
|
||||
"path"
|
||||
)
|
||||
|
||||
//go:embed *
|
||||
var templateFS embed.FS
|
||||
|
||||
func TemplateLoader() pongo2.TemplateLoader {
|
||||
return &templateLoader{templateFS}
|
||||
}
|
||||
|
||||
type templateLoader struct {
|
||||
embed.FS
|
||||
}
|
||||
|
||||
func (tl *templateLoader) Abs(base, name string) string {
|
||||
return path.Join(path.Dir(base), name)
|
||||
}
|
||||
|
||||
func (tl *templateLoader) Get(name string) (io.Reader, error) {
|
||||
f, err := tl.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
22
analytics/webui/internal/templates/logs-from-session.html
Normal file
22
analytics/webui/internal/templates/logs-from-session.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends "extendable/base.html" %}
|
||||
|
||||
{% block main %}
|
||||
<a href="/">[< Back]</a>
|
||||
|
||||
<h2>Logs from session</h2>
|
||||
|
||||
<p>
|
||||
Session ID: {{ session.ID }}<br>
|
||||
IP address: {{ session.IPAddr }}<br>
|
||||
User agent: {% if session.UserAgent %}{{ session.UserAgent }}{% else %}<span class="italic">unset</span>{% endif %}
|
||||
</p>
|
||||
|
||||
<div hx-get="/partial/sessionLogs/{{ session.ID }}"
|
||||
hx-trigger="load"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
76
analytics/webui/session.go
Normal file
76
analytics/webui/session.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/codemicro/analytics/analytics/db/models"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"html"
|
||||
)
|
||||
|
||||
func (wui *WebUI) page_logsFromSession(ctx *fiber.Ctx) error {
|
||||
id := ctx.Query("id")
|
||||
|
||||
pctx := make(pongo2.Context)
|
||||
|
||||
if id == "" {
|
||||
return ctx.RedirectBack("/")
|
||||
}
|
||||
|
||||
session := new(models.Session)
|
||||
if err := wui.db.DB.NewSelect().Model(session).Where("id = ?", id).Scan(context.Background(), session); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return fiber.ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
pctx["session"] = session
|
||||
|
||||
return wui.sendTemplate(ctx, "logs-from-session.html", pctx)
|
||||
}
|
||||
|
||||
func (wui *WebUI) partial_logsFromSession(ctx *fiber.Ctx) error {
|
||||
id := ctx.Params("id")
|
||||
pa := fmt.Sprintf("/partial/sessionLogs/%s", html.EscapeString(id))
|
||||
|
||||
ht := &HTMLTable{
|
||||
Path: pa,
|
||||
Headers: []*HTMLTableHeader{
|
||||
{"Datetime", "time", true, false},
|
||||
{"Host", "", false, false},
|
||||
{"Raw path", "raw_uri", true, false},
|
||||
{"Status", "status_code", true, false},
|
||||
{"Referer", "", false, false},
|
||||
},
|
||||
Data: func(sortKey, sortDirection string) ([][]any, error) {
|
||||
var reqs []*models.Request
|
||||
q := wui.db.DB.NewSelect().Model(&reqs).Where("session_id = ?", id)
|
||||
if sortKey != "" {
|
||||
q = q.Order(sortKey + " " + sortDirection)
|
||||
}
|
||||
if err := q.Scan(context.Background(), &reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res [][]any
|
||||
for _, request := range reqs {
|
||||
res = append(res, []any{
|
||||
getValue(pongo2.ApplyFilter("date", pongo2.AsValue(request.Time), pongo2.AsValue("2006-01-02 15:04:05"))),
|
||||
request.Host,
|
||||
request.RawURI,
|
||||
request.StatusCode,
|
||||
getValue(pongo2.ApplyFilter("default", pongo2.AsValue(request.Referer), unsetValue)),
|
||||
})
|
||||
}
|
||||
|
||||
return res, nil
|
||||
},
|
||||
DefaultSortKey: "time",
|
||||
DefaultSortDirection: "desc",
|
||||
ShowNumberOfEntries: true,
|
||||
}
|
||||
return wui.renderHTMLTable(ctx, ht)
|
||||
}
|
65
analytics/webui/static/assets/css/main.css
Normal file
65
analytics/webui/static/assets/css/main.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
body {
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
div.pt {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.card-deck {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
div.card-deck div.post {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
div.card-deck div.post:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
div.card-deck {
|
||||
flex-direction: column;
|
||||
}
|
||||
div.card-deck div.card {
|
||||
margin-right: unset;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
div.card-deck div.card:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.table table {
|
||||
width: 60%;
|
||||
white-space: nowrap;
|
||||
border-collapse: collapse;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
div.table table tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
div.table table tr:hover {
|
||||
background-color: #dddddd;
|
||||
}
|
||||
div.table table td, div.table table th {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
div.table table th {
|
||||
border-bottom: 3px solid black;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=main.css.map */
|
1
analytics/webui/static/assets/css/main.css.map
Normal file
1
analytics/webui/static/assets/css/main.css.map
Normal file
|
@ -0,0 +1 @@
|
|||
{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;EACE;EAEA;EACA;EAEA;EACA;;;AAgBF;EAEE;;;AAKF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;AAIA;EACE;EACA,cAJY;;AAOd;EACE;;AAGF;EAfF;IAgBI;;EAEA;IAAW;IAAqB,eAdpB;;EAeZ;IAAsB,eAfV;;;;AAoBd;EACE;EACA;EACA;EAEA;;AAGE;EAAoB;;AACpB;EAAU;;AAGZ;EACE;;AAGF;EACE;EACA","file":"main.css"}
|
85
analytics/webui/static/assets/css/main.scss
Normal file
85
analytics/webui/static/assets/css/main.scss
Normal file
|
@ -0,0 +1,85 @@
|
|||
body {
|
||||
line-height: 1.3;
|
||||
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
font-size: 18px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
@mixin container {
|
||||
width: 55%;
|
||||
margin: 0 auto;
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1100px) and (min-width: 801px) {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
//@include container;
|
||||
padding: 15px;
|
||||
//padding-top: 15px;
|
||||
//padding-bottom: 15px;
|
||||
}
|
||||
|
||||
div.pt {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.card-deck {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
$margin-size: 10px;
|
||||
|
||||
div.post {
|
||||
flex: 1;
|
||||
margin-right: $margin-size;
|
||||
}
|
||||
|
||||
div.post:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
flex-direction: column;
|
||||
|
||||
div.card { margin-right: unset; margin-bottom: $margin-size; }
|
||||
div.card:last-child { margin-bottom: $margin-size; }
|
||||
}
|
||||
}
|
||||
|
||||
div.table {
|
||||
table {
|
||||
width: 60%;
|
||||
white-space: nowrap;
|
||||
border-collapse: collapse;
|
||||
|
||||
overflow-y: scroll;
|
||||
|
||||
tr {
|
||||
&:nth-child(even) { background-color: #f2f2f2; }
|
||||
&:hover { background-color: #dddddd; }
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
border-bottom: 3px solid black;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
74
analytics/webui/tables.go
Normal file
74
analytics/webui/tables.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HTMLTable struct {
|
||||
Path string
|
||||
Headers []*HTMLTableHeader
|
||||
Data func(sortKey, sortDirection string) ([][]any, error)
|
||||
DefaultSortKey string
|
||||
DefaultSortDirection string
|
||||
ShowNumberOfEntries bool
|
||||
}
|
||||
|
||||
type HTMLTableHeader struct {
|
||||
Name string
|
||||
Slug string
|
||||
Sortable bool
|
||||
Safe bool
|
||||
}
|
||||
|
||||
var unsetValue = pongo2.AsSafeValue(`<span class="italic">unset</span>`)
|
||||
|
||||
func (wui *WebUI) renderHTMLTable(ctx *fiber.Ctx, ht *HTMLTable) error {
|
||||
sortKey := ctx.Query("sortKey")
|
||||
sortDirection := strings.ToLower(ctx.Query("sortDir"))
|
||||
|
||||
{
|
||||
var validatedSortKey string
|
||||
for _, header := range ht.Headers {
|
||||
if strings.EqualFold(header.Slug, sortKey) && header.Sortable {
|
||||
validatedSortKey = header.Slug
|
||||
break
|
||||
}
|
||||
}
|
||||
sortKey = validatedSortKey
|
||||
}
|
||||
|
||||
if sortKey == "" {
|
||||
sortKey = ht.DefaultSortKey
|
||||
}
|
||||
|
||||
if sortDirection == "" || !(sortDirection == "asc" || sortDirection == "desc") {
|
||||
fmt.Println("ere", sortDirection, sortKey)
|
||||
if sortKey == "" {
|
||||
sortDirection = ""
|
||||
} else {
|
||||
sortDirection = ht.DefaultSortDirection
|
||||
}
|
||||
}
|
||||
|
||||
data, err := ht.Data(sortKey, sortDirection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Type("html")
|
||||
return wui.sendTemplate(ctx, "components/table.html", pongo2.Context{
|
||||
"path": ht.Path,
|
||||
"headers": ht.Headers,
|
||||
"rows": data,
|
||||
"sortKey": sortKey,
|
||||
"sortDirection": sortDirection,
|
||||
"showNumberOfEntries": ht.ShowNumberOfEntries,
|
||||
})
|
||||
}
|
||||
|
||||
func getValue(v *pongo2.Value, _ *pongo2.Error) *pongo2.Value {
|
||||
return v
|
||||
}
|
|
@ -1,10 +1,16 @@
|
|||
package webui
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/codemicro/analytics/analytics/config"
|
||||
"github.com/codemicro/analytics/analytics/db"
|
||||
"github.com/codemicro/analytics/analytics/webui/internal/templates"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -12,7 +18,37 @@ type WebUI struct {
|
|||
conf *config.Config
|
||||
db *db.DB
|
||||
|
||||
app *fiber.App
|
||||
app *fiber.App
|
||||
templates *pongo2.TemplateSet
|
||||
}
|
||||
|
||||
func init() {
|
||||
pongo2.RegisterFilter("shortTimeSince", func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||
tn := time.Now().UTC()
|
||||
t := in.Time()
|
||||
dur := tn.Sub(t).Round(time.Second)
|
||||
|
||||
var (
|
||||
qty int
|
||||
descriptor string
|
||||
)
|
||||
|
||||
if int(dur.Minutes()) != 0 {
|
||||
qty = int(dur.Minutes())
|
||||
descriptor = "minute"
|
||||
} else if int(dur.Seconds()) > 30 {
|
||||
qty = int(dur.Seconds())
|
||||
descriptor = "second"
|
||||
} else {
|
||||
return pongo2.AsValue("just now"), nil
|
||||
}
|
||||
|
||||
if qty != 1 {
|
||||
descriptor += "s"
|
||||
}
|
||||
|
||||
return pongo2.AsValue(fmt.Sprintf("%d %s ago", qty, descriptor)), nil
|
||||
})
|
||||
}
|
||||
|
||||
func Start(conf *config.Config, db *db.DB) *WebUI {
|
||||
|
@ -20,23 +56,54 @@ func Start(conf *config.Config, db *db.DB) *WebUI {
|
|||
conf: conf,
|
||||
db: db,
|
||||
}
|
||||
wui.app = fiber.New()
|
||||
|
||||
wui.app = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: !config.Debug,
|
||||
})
|
||||
wui.registerHandlers()
|
||||
|
||||
wui.templates = pongo2.NewSet("templates", templates.TemplateLoader())
|
||||
|
||||
go func() {
|
||||
if err := wui.app.Listen(conf.HTTP.Address); err != nil {
|
||||
log.Error().Err(err).Msg("HTTP server listen failed")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info().Msgf("HTTP server alive on %s", conf.HTTP.Address)
|
||||
return wui
|
||||
}
|
||||
|
||||
func (wui *WebUI) sendTemplate(ctx *fiber.Ctx, fname string, renderCtx pongo2.Context) error {
|
||||
tpl, err := wui.templates.FromFile(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := tpl.ExecuteBytes(renderCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Type("html")
|
||||
return ctx.Send(res)
|
||||
}
|
||||
|
||||
func (wui *WebUI) Stop() error {
|
||||
return wui.app.ShutdownWithTimeout(time.Second * 5)
|
||||
}
|
||||
|
||||
//go:embed static/*
|
||||
var static embed.FS
|
||||
|
||||
func (wui *WebUI) registerHandlers() {
|
||||
wui.app.Get("/", func(ctx *fiber.Ctx) error {
|
||||
return ctx.SendString("Hello! This is the HTTP server.")
|
||||
})
|
||||
}
|
||||
wui.app.Get("/", wui.page_index)
|
||||
wui.app.Get("/partial/activeSessions", wui.partial_activeSessionsTable)
|
||||
|
||||
wui.app.Get("/session", wui.page_logsFromSession)
|
||||
wui.app.Get("/partial/sessionLogs/:id", wui.partial_logsFromSession)
|
||||
|
||||
wui.app.Use("/", filesystem.New(filesystem.Config{
|
||||
Root: http.FS(static),
|
||||
PathPrefix: "static",
|
||||
}))
|
||||
}
|
||||
|
|
3
go.mod
3
go.mod
|
@ -13,6 +13,8 @@ require (
|
|||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.14.1 // indirect
|
||||
github.com/flosch/pongo2/v6 v6.0.0 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.43.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
|
@ -31,6 +33,7 @@ require (
|
|||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/tinylib/msgp v1.1.8 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.12 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.45.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -6,6 +6,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
|
||||
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofiber/fiber/v2 v2.43.0 h1:yit3E4kHf178B60p5CQBa/3v+WVuziWMa/G2ZNyLJB0=
|
||||
github.com/gofiber/fiber/v2 v2.43.0/go.mod h1:mpS1ZNE5jU+u+BA4FbM+KKnUzJ4wzTK+FT2tG3tU+6I=
|
||||
|
@ -68,6 +72,8 @@ github.com/uptrace/bun/dialect/sqlitedialect v1.1.12 h1:Ud31nqZmebcQpl151nb108+v
|
|||
github.com/uptrace/bun/dialect/sqlitedialect v1.1.12/go.mod h1:Pwg7s31BdF3PMBlWTnYkEn2I9ASsvatt1Ln/AERCTV4=
|
||||
github.com/uptrace/bun/driver/sqliteshim v1.1.12 h1:GMbSa7Pjjk4kjF8XURz5uMLe2PbN98e6t00sp0rx2Eo=
|
||||
github.com/uptrace/bun/driver/sqliteshim v1.1.12/go.mod h1:u67g2ewzoMDCCAqjliHAM/BJjEXfoExXlFXhx3TnXRs=
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.12 h1:y8nrHvo7TUCR91kXngWuF7Bk0E1nCTsWzYL1CDEriTo=
|
||||
github.com/uptrace/bun/extra/bundebug v1.1.12/go.mod h1:psjCrCMf5JaAyivW/A8MDBW5MwIy/jZFBCkIaBgabtM=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
|
||||
|
|
Reference in a new issue