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(`
Register Host %d hosts
%s
`, len(hosts), tiles.String())) } func hostTile(h model.Host) string { stateClass := stateColor(h.State) led := ledClass(h.State) return fmt.Sprintf(`
%s
%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(`

%s

%s
MAC%s
Server Type%s
IP Address%s
Notes%s
%s

Operations

%s
KindStateStartedDurationError
`, 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(`
Upload Image %d images
%s
NameKindVersionDefaultAdded
`, 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
`, 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) }