Skip to content

Commit

Permalink
Add new UI and sse support
Browse files Browse the repository at this point in the history
  • Loading branch information
heppu committed Mar 16, 2024
1 parent 3f0cb0d commit 889eeb4
Show file tree
Hide file tree
Showing 31 changed files with 1,415 additions and 828 deletions.
68 changes: 55 additions & 13 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
package api

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"io/fs"
"log/slog"
"net/http"

"github.com/a-h/templ"
"github.com/gevulotnetwork/devnet-explorer/api/templates"
"github.com/gevulotnetwork/devnet-explorer/model"
"github.com/julienschmidt/httprouter"
)

//go:embed all:public
var public embed.FS
//go:embed all:assets
var assets embed.FS

type Store interface {
Stats() (model.Stats, error)
Events() <-chan model.Event
}

type API struct {
r *httprouter.Router
r *http.ServeMux
s Store
}

func New(s Store) (*API, error) {
a := &API{
r: httprouter.New(),
r: http.NewServeMux(),
s: s,
}

publicFS, err := fs.Sub(public, "public")
assetsFS, err := fs.Sub(assets, "assets")
if err != nil {
return nil, fmt.Errorf("failed to create public fs: %w", err)
return nil, err
}

a.r.NotFound = http.FileServer(http.FS(publicFS))
a.r.GET("/api/v1/stats", a.stats)
a.r.Handle("GET /", templ.Handler(templates.Index()))
a.r.HandleFunc("GET /api/v1/stats", a.stats)
a.r.HandleFunc("GET /api/v1/events", a.events)
a.r.Handle("GET /assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(assetsFS))))

return a, nil
}
Expand All @@ -45,16 +49,54 @@ func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.r.ServeHTTP(w, r)
}

func (a *API) stats(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
func (a *API) stats(w http.ResponseWriter, r *http.Request) {
stats, err := a.s.Stats()
if err != nil {
slog.Error("failed to get stats", slog.Any("error", err))
http.Error(w, "failed to get stats", http.StatusInternalServerError)
return
}

if err := json.NewEncoder(w).Encode(stats); err != nil {
slog.Error("failed encode stats", slog.Any("error", err))
if r.Header.Get("Accept") == "application/json" {
if err := json.NewEncoder(w).Encode(stats); err != nil {
slog.Error("failed encode stats", slog.Any("error", err))
return
}
return
}

if err := templates.Stats(stats).Render(r.Context(), w); err != nil {
slog.Error("failed render stats", slog.Any("error", err))
return
}
}

func (a *API) events(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

slog.Info("client connected", slog.String("remote_addr", r.RemoteAddr))

for {
select {
case <-r.Context().Done(): // Client disconnected
slog.Info("client disconnected", slog.String("remote_addr", r.RemoteAddr))
return
case event, ok := <-a.s.Events():
if !ok {
return
}

buf := &bytes.Buffer{}
buf.WriteString("data: ")
if err := templates.Row(event).Render(r.Context(), buf); err != nil {
slog.Error("failed render row", slog.Any("error", err))
return
}
buf.WriteString("\n\n")
buf.WriteTo(w)
w.(http.Flusher).Flush()
}
}
}
Binary file added api/assets/Inter-Black.ttf
Binary file not shown.
Binary file added api/assets/Inter-Bold.ttf
Binary file not shown.
Binary file added api/assets/Inter-ExtraBold.ttf
Binary file not shown.
Binary file added api/assets/Inter-ExtraLight.ttf
Binary file not shown.
Binary file added api/assets/Inter-Light.ttf
Binary file not shown.
Binary file added api/assets/Inter-Medium.ttf
Binary file not shown.
Binary file added api/assets/Inter-Regular.ttf
Binary file not shown.
Binary file added api/assets/Inter-SemiBold.ttf
Binary file not shown.
Binary file added api/assets/Inter-Thin.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions api/assets/htmx.min.js

Large diffs are not rendered by default.

Loading

0 comments on commit 889eeb4

Please sign in to comment.