package api
import (
"fmt"
"html"
"net/http"
"strings"
"provisioning/internal/model"
)
func renderHTML(w http.ResponseWriter, body string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(body))
}
func dashboardPage(hosts []model.Host) string {
var tiles strings.Builder
for _, h := range hosts {
tiles.WriteString(hostTile(h))
}
if len(hosts) == 0 {
tiles.WriteString(`
No hosts registered. Register one.
`)
}
return layout("Dashboard", fmt.Sprintf(`
%s
`, len(hosts), tiles.String()))
}
func hostTile(h model.Host) string {
stateClass := stateColor(h.State)
led := ledClass(h.State)
return fmt.Sprintf(`
%s
`, h.ID, stateClass, h.ID, led, html.EscapeString(h.Hostname), html.EscapeString(h.ServerType), h.MAC, h.State)
}
func hostFormPage(types []string, errMsg string, prefill *model.Host) string {
var opts strings.Builder
for _, t := range types {
selected := ""
if prefill != nil && prefill.ServerType == t {
selected = " selected"
}
opts.WriteString(fmt.Sprintf(``, t, selected, t))
}
errHTML := ""
if errMsg != "" {
errHTML = fmt.Sprintf(`%s
`, html.EscapeString(errMsg))
}
hostname, mac, notes := "", "", ""
if prefill != nil {
hostname = html.EscapeString(prefill.Hostname)
mac = html.EscapeString(prefill.MAC)
notes = html.EscapeString(prefill.Notes)
}
return layout("Register Host", fmt.Sprintf(`
Register Host
%s
`, errHTML, hostname, mac, opts.String(), notes))
}
func hostDetailPage(h *model.Host, ops []model.Operation) string {
stateClass := stateColor(h.State)
led := ledClass(h.State)
canRebuild := h.State == model.StateRegistered || h.State == model.StateReady || h.State == model.StateFailed
var actions strings.Builder
if canRebuild {
actions.WriteString(fmt.Sprintf(``, h.ID))
}
actions.WriteString(fmt.Sprintf(``, h.ID))
var opsHTML strings.Builder
for _, op := range ops {
duration := ""
if op.CompletedAt != nil {
duration = op.CompletedAt.Sub(op.StartedAt).Truncate(1e9).String()
}
errCell := ""
if op.ErrorMessage != "" {
errCell = html.EscapeString(op.ErrorMessage)
}
opsHTML.WriteString(fmt.Sprintf(`| %s | %s | %s | %s | %s |
`,
op.Kind, op.State, op.StartedAt.Format("2006-01-02 15:04"), duration, errCell))
}
ip := h.IPAddress
if ip == "" {
ip = "—"
}
return layout(h.Hostname, fmt.Sprintf(`
| MAC | %s |
| Server Type | %s |
| IP Address | %s |
| Notes | %s |
%s
Operations
| Kind | State | Started | Duration | Error |
%s
`, led, html.EscapeString(h.Hostname), stateClass, h.State, h.MAC, h.ServerType, ip, html.EscapeString(h.Notes), actions.String(), opsHTML.String()))
}
func imagesPage(images []model.Image) string {
var rows strings.Builder
for _, img := range images {
def := ""
if img.IsDefault {
def = `default`
} else {
def = fmt.Sprintf(``, img.ID)
}
deleteBtn := fmt.Sprintf(``, img.ID, html.EscapeString(img.Name))
rows.WriteString(fmt.Sprintf(`| %s | %s | %s | %s | %s | %s |
`,
html.EscapeString(img.Name), img.Kind, img.Version, def, img.CreatedAt.Format("2006-01-02"), deleteBtn))
}
if len(images) == 0 {
rows.WriteString(`| No images uploaded yet. |
`)
}
return layout("Images", fmt.Sprintf(`
| Name | Kind | Version | Default | Added | |
%s
`, len(images), rows.String()))
}
func imageUploadForm(errMsg string) string {
errHTML := ""
if errMsg != "" {
errHTML = fmt.Sprintf(`%s
`, html.EscapeString(errMsg))
}
return layout("Upload Image", fmt.Sprintf(`
Upload Boot Image
%s
Uploading ISO...
Preparing upload...
`, errHTML))
}
func stateColor(s model.HostState) string {
switch s {
case model.StateRegistered:
return "state-grey"
case model.StatePXEReady, model.StatePXEBooted, model.StateInstalling:
return "state-blue"
case model.StateInstalled, model.StateFirstBoot, model.StateJoining:
return "state-amber"
case model.StateReady:
return "state-green"
case model.StateFailed:
return "state-red"
default:
return "state-grey"
}
}
func ledClass(s model.HostState) string {
switch s {
case model.StateRegistered:
return "led-grey"
case model.StatePXEReady, model.StatePXEBooted, model.StateInstalling:
return "led-blue"
case model.StateInstalled, model.StateFirstBoot, model.StateJoining:
return "led-amber"
case model.StateReady:
return "led-green"
case model.StateFailed:
return "led-red"
default:
return "led-grey"
}
}
func layout(title, body string) string {
return fmt.Sprintf(`
%s — Provisioning
%s
`, html.EscapeString(title), body)
}