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.StartLivetracking(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.Infoln("close websocket connection", err) c.Close() break } } } } func fiberTemplateEngine(c *core.Configuration) *html.Engine { // Create a new engine by passing the template folder // and template extension using .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) }