gyrogpsc/web/http.go

259 lines
7.2 KiB
Go

package web
import (
"errors"
"git.timovolkmann.de/gyrogpsc/core"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html"
"github.com/gofiber/websocket/v2"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
func CreateServer(s *core.TrackingService, sub core.Subscriber, c *core.Configuration) {
app := fiber.New(fiber.Config{
Views: fiberTemplateEngine(c),
})
app.Static("/static", "static")
// Application Main Page
app.Get("/", fiberHomeHandler)
app.Get("/tracking", fiberTrackingHandler)
// Websocket
app.Get("/ws", websocket.New(createFiberWebsocketHandler(sub)))
// Tracking persistence controls HTTP JSON RPC API
trackings := app.Group("/trackings")
trackings.Get("/", allTrackingsHandler(s, c)) // Get all trackings Metadata
trackings.Post("/", startPipelineHandler(s, c)) // Initialize new tracking, open websocket and prepare for automatic recording. Toggle ?serial=true and ?tcp=true. Returns trackingId
trackings.Patch("/", startRecordingHandler(s, c)) // Starts recording
trackings.Put("/", stopRecordingHandler(s, c)) // Stops current recording. Returns trackingId if record was successful
trackings.Delete("/", stopAllHandler(s, c)) // Stops websocket connection, pipelines and collectors
trackings.Get("/:trackingId", LoadTrackingHandler(s, c)) // Gets Tracking Metadata and loads sensorRecords from storage.
logrus.Fatal(app.Listen(c.Webserver.Port))
}
func LoadTrackingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
trackId := ctx.Params("trackingId")
uid, err := uuid.Parse(trackId)
if err != nil {
logrus.Error(err)
ctx.Status(404).JSON(err)
return err
}
var replay bool
if ctx.Query("replay", "false") == "true" {
replay = true
} else {
replay = false
}
tracking, err := s.LoadTracking(uid, replay)
if err != nil {
logrus.Error(err)
ctx.Status(404).JSON(err)
return err
}
st := struct {
core.TrackingMetadata
Data map[string][]core.SensorData
}{}
st.TrackingMetadata = tracking.TrackingMetadata
st.Data = make(map[string][]core.SensorData)
for _, el := range tracking.Data {
st.Data[string(el.Source())] = append(st.Data[string(el.Source())], el)
}
prepres := make(map[string]interface{})
prepres["data"] = st
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func allTrackingsHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
trackings, err := s.AllTrackings()
prepres := make(map[string]interface{})
prepres["data"] = trackings
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func startPipelineHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
var collecs []core.CollectorType
ser := ctx.Query("serial", "true")
if ser == "true" {
collecs = append(collecs, core.SERIAL)
} else if ser != "false" && ser != "" {
collecs = append(collecs, core.CollectorType(ser)) // TODO: allow passing serial port as url parameter
}
tcp := ctx.Query("tcp", "true")
logrus.Debugln("query values: serial/tcp", ser, tcp)
if tcp == "true" {
collecs = append(collecs, core.TCP)
}
res, err := s.StartPipeline(collecs...)
prepres := make(map[string]interface{})
prepres["tracking_state"] = res
prepres["data"] = collecs
if err != nil {
prepres["error"] = err.Error()
}
if len(collecs) == 0 {
e := errors.New("attention! no collectors running. start a new pipeline")
prepres["error"] = e.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func startRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StartRecord()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "RECORD"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func stopRecordingHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopRecord()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "LIVE"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func stopAllHandler(s *core.TrackingService, c *core.Configuration) fiber.Handler {
return func(ctx *fiber.Ctx) error {
rec, err := s.StopAll()
prepres := make(map[string]interface{})
prepres["tracking_state"] = "STOPPED"
prepres["data"] = rec
if err != nil {
prepres["error"] = err.Error()
}
err2 := ctx.JSON(prepres)
if err2 != nil {
ctx.Status(500).JSON(err2)
return err2
}
return nil
}
}
func createFiberWebsocketHandler(s core.Subscriber) func(conn *websocket.Conn) {
return func(c *websocket.Conn) {
logrus.Info("new websocket client")
// Handle and discard inbound messages
go func() {
for {
if _, _, err := c.NextReader(); err != nil {
c.Close()
break
}
}
}()
dispatcherId, channel := s.Subscribe()
defer s.Unsubscribe(dispatcherId)
for {
cmsg := <-channel
logrus.Traceln("write to ws:", cmsg)
err := c.WriteMessage(websocket.TextMessage, []byte(cmsg))
if err != nil {
logrus.Info("close websocket connection")
c.Close()
break
}
}
}
}
func fiberTemplateEngine(c *core.Configuration) *html.Engine {
// Create a new engine by passing the template folder
// and template extension using <engine>.New(dir, ext string)
engine := html.New("./templates", ".html")
// We also support the http.FileSystem interface
// See examples below to load templates from embedded files
// engine := html.NewFileSystem(http.Dir("./views"), ".html")
// Reload the templates on each render, good for development
//engine.Reload(strings.ToLower(c.Debuglevel) == "debug") // Optional. Default: false
engine.Reload(true) // Optional. Default: false
// Debug will print each template that is parsed, good for debugging
//engine.Debug(strings.ToLower(c.Debuglevel) == "debug") // Optional. Default: false
engine.Debug(true) // Optional. Default: false
// Layout defines the variable name that is used to yield templates within layouts
//engine.Layout("embed") // Optional. Default: "embed"
// Delims sets the action delimiters to the specified strings
//engine.Delims("{{", "}}") // Optional. Default: engine delimiters
// AddFunc adds a function to the template's global function map.
//engine.AddFunc("greet", func(name string) string {
// return "Hello, " + name + "!"
//})
return engine
}
func fiberHomeHandler(c *fiber.Ctx) error {
// Render index template
return c.Render("index", "ws://"+c.Hostname()+"/ws")
}
func fiberTrackingHandler(c *fiber.Ctx) error {
// Render index template
return c.Render("replayFull", nil)
}