diff --git a/bootserver/controllers/ui/static.go b/bootserver/controllers/ui/static.go new file mode 100644 index 0000000..8c5d687 --- /dev/null +++ b/bootserver/controllers/ui/static.go @@ -0,0 +1,15 @@ +package ui + +import ( + "net/http" +) + +const StaticRoute = "/static/" + +type StaticController struct { +} + +func (sc *StaticController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fs := http.FileServer(http.Dir("./static")) + http.StripPrefix(StaticRoute, fs).ServeHTTP(w, r) +} diff --git a/bootserver/controllers/ui/ui.go b/bootserver/controllers/ui/ui.go new file mode 100644 index 0000000..7a25681 --- /dev/null +++ b/bootserver/controllers/ui/ui.go @@ -0,0 +1,96 @@ +package ui + +import ( + "bytes" + "fmt" + "html/template" + "io" + "net/http" + "path/filepath" + + "git.faercol.me/faercol/http-boot-server/bootserver/helpers" + "git.faercol.me/faercol/http-boot-server/bootserver/services" + "github.com/sirupsen/logrus" +) + +const UIRoute = "/" + +type templateBootOption struct { + ID string + Name string + Path string + Selected bool +} + +type templateClient struct { + ID string + Name string + BootOptions []templateBootOption +} + +type templateData struct { + Clients []templateClient +} + +type UIController struct { + clientService *services.ClientHandlerService + l *logrus.Logger +} + +func NewUIController(l *logrus.Logger, service *services.ClientHandlerService) *UIController { + return &UIController{ + clientService: service, + l: l, + } +} + +func (uc *UIController) serveUI(w http.ResponseWriter, r *http.Request) (int, int, error) { + lp := filepath.Join("templates", "index.html") + tmpl, err := template.ParseFiles(lp) + if err != nil { + return http.StatusInternalServerError, -1, fmt.Errorf("failed to init template: %w", err) + } + buf := new(bytes.Buffer) + + clients, err := uc.clientService.GetAllClientConfig() + if err != nil { + return http.StatusInternalServerError, -1, fmt.Errorf("failed to get list of clients: %w", err) + } + + dat := templateData{Clients: []templateClient{}} + for _, clt := range clients { + tplBO := []templateBootOption{} + for id, bo := range clt.Options { + tplBO = append(tplBO, templateBootOption{ + Name: bo.Name, + Path: bo.Path, + ID: id, + Selected: id == clt.SelectedOption, + }) + } + dat.Clients = append(dat.Clients, templateClient{ + ID: clt.ID.String(), + Name: clt.Name, + BootOptions: tplBO, + }) + } + + if err := tmpl.Execute(buf, dat); err != nil { + return http.StatusInternalServerError, -1, fmt.Errorf("failed to execute template: %w", err) + } + n, err := io.Copy(w, buf) + if err != nil { + return http.StatusInternalServerError, int(n), fmt.Errorf("failed to write response; %w", err) + } + + return http.StatusOK, int(n), nil +} + +func (uc *UIController) ServeHTTP(w http.ResponseWriter, r *http.Request) { + returncode, contentLength, err := uc.serveUI(w, r) + if err != nil { + uc.l.Errorf("Error serving UI: %s", err.Error()) + helpers.HandleResponse(w, r, returncode, nil, uc.l) + } + helpers.AddToContext(r, returncode, contentLength) +} diff --git a/bootserver/helpers/helpers.go b/bootserver/helpers/helpers.go index 5a6e659..035ed63 100644 --- a/bootserver/helpers/helpers.go +++ b/bootserver/helpers/helpers.go @@ -29,3 +29,8 @@ func HandleResponse(w http.ResponseWriter, r *http.Request, returncode int, cont contextedReq := r.WithContext(context.WithValue(r.Context(), ResponseInfoKey, ResponseInfo{ReturnCode: returncode, ContentLength: len(content)})) *r = *contextedReq } + +func AddToContext(r *http.Request, returncode, contentLength int) { + contextedReq := r.WithContext(context.WithValue(r.Context(), ResponseInfoKey, ResponseInfo{ReturnCode: returncode, ContentLength: contentLength})) + *r = *contextedReq +} diff --git a/bootserver/server/server.go b/bootserver/server/server.go index e4a216c..f245a3d 100644 --- a/bootserver/server/server.go +++ b/bootserver/server/server.go @@ -11,6 +11,7 @@ import ( "git.faercol.me/faercol/http-boot-server/bootserver/bootoption" "git.faercol.me/faercol/http-boot-server/bootserver/config" "git.faercol.me/faercol/http-boot-server/bootserver/controllers/client" + "git.faercol.me/faercol/http-boot-server/bootserver/controllers/ui" "git.faercol.me/faercol/http-boot-server/bootserver/middlewares" "git.faercol.me/faercol/http-boot-server/bootserver/services" "github.com/sirupsen/logrus" @@ -68,6 +69,8 @@ func New(appConf *config.AppConfig, logger *logrus.Logger) (*Server, error) { controllers := map[string]http.Handler{ client.EnrollRoute: middlewares.WithLogger(client.NewEnrollController(logger, service), logger), client.ConfigRoute: middlewares.WithLogger(client.NewGetConfigController(logger, service, appConf), logger), + ui.StaticRoute: &ui.StaticController{}, + ui.UIRoute: middlewares.WithLogger(ui.NewUIController(logger, service), logger), } m := http.NewServeMux() diff --git a/bootserver/services/services.go b/bootserver/services/services.go index 2a2df32..4eedb04 100644 --- a/bootserver/services/services.go +++ b/bootserver/services/services.go @@ -99,6 +99,21 @@ func (chs *ClientHandlerService) AddClient(client *bootoption.Client) (uuid.UUID return client.ID, nil } +func (chs *ClientHandlerService) GetAllClientConfig() ([]*bootoption.Client, error) { + clients, err := chs.load() + if err != nil { + return nil, fmt.Errorf("failed to load current config %w", err) + } + defer chs.unloadNoCommmit(clients) + clientList := []*bootoption.Client{} + + for id, clt := range clients { + clt.ID = id + clientList = append(clientList, clt) + } + return clientList, nil +} + func (chs *ClientHandlerService) GetClientConfig(client uuid.UUID) (*bootoption.Client, error) { clients, err := chs.load() if err != nil { diff --git a/bootserver/static/stylesheets/main.css b/bootserver/static/stylesheets/main.css new file mode 100644 index 0000000..729c094 --- /dev/null +++ b/bootserver/static/stylesheets/main.css @@ -0,0 +1,70 @@ +body { + background-color: black; + color: white; +} + +.main-container { + border-radius: 5px; + background-color: #121212; + padding: 20px; +} + +.title-container { + border-radius: 5px 5px 0px 0px; + padding: 5px; + background-color: #1A3A5D; +} + +.client-list-container { + border-radius: 0px 0px 5px 5px; + padding: 10px; + background-color: #232323; +} + +.client-container { + border-radius: 5px; + padding: 4px; + margin-top: 2px; + margin-bottom: 2px; +} + +.client-header { + .client-name { + font-size: large; + font-weight: bold; + } + + .client-uuid { + font-size: smaller; + color: #9B9B9B; + } +} + +.client-boot-options { + padding-left: 10px; + padding-right: 10px; +} + +.client-boot-option { + margin: 3px auto; + padding: 8px 8px; + + &.selected { + background-color: #3E4042; + } +} + +.boot-option-name { + font-size: large; + font-weight: bold; +} + +.boot-option-uuid { + font-size: smaller; + color: #9B9B9B; +} + +.boot-option-path { + font-size: small; + padding-left: 10px; +} \ No newline at end of file diff --git a/bootserver/templates/index.html b/bootserver/templates/index.html new file mode 100644 index 0000000..3208884 --- /dev/null +++ b/bootserver/templates/index.html @@ -0,0 +1,48 @@ + + + + + + HTTP boot server + + + + +
+ +
+

HTTP boot server admin page

+
+ +
+

Currently enrolled clients

+ + {{range .Clients}} +
+
+ {{.Name}} + {{.ID}} +
+
+
+ {{range .BootOptions}} +
+
+ {{.Name}} + {{.ID}} +
+
+ {{.Path}} +
+
+ {{end}} +
+
+
+ {{end}} +
+ +
+ + + \ No newline at end of file