Files
bible/demo/internal/web/patterns_more.go

1190 lines
36 KiB
Go

package web
import (
"bytes"
"encoding/csv"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
)
type controlsRow struct {
ID int
Name string
Type string
Status string
Selected bool
ToggleURL string
EditURL string
RemoveURL string
}
type controlsPageData struct {
Title string
CurrentPath string
Rows []controlsRow
Segment string
Page int
Pager tableDemoPager
VisibleCount int
SelectedCount int
SelectedVisible int
SelectedHidden int
ActionMessage string
SegmentedCounts map[string]int
SegmentURLs map[string]string
SelectVisibleURL string
SelectFilteredURL string
ClearVisibleURL string
ClearFilteredURL string
ClearSelectionURL string
BulkReviewURL string
BulkArchiveURL string
BulkExportURL string
BulkRetrySyncURL string
OpenEditSelectedURL string
OpenDeleteSelectedURL string
HasSelection bool
SimulateLoading bool
SimulateLoadingURL string
ClearLoadingURL string
}
type modalDemoPageData struct {
Title string
CurrentPath string
Open string
Stage string
Message string
SelectedIDs []string
}
type ioImportPreviewRow struct {
RowNo int
ItemCode string
Name string
Qty int
Status string
}
type ioPageData struct {
Title string
CurrentPath string
ImportMode string
FileName string
ImportMessage string
PreviewRows []ioImportPreviewRow
ExportFormat string
ExportScope string
ExportMessage string
}
type formsDemoPageData struct {
Title string
CurrentPath string
Mode string
Step string
ServerSerial string
Location string
ComponentSerial string
EventDate string
Details string
Message string
FieldErrors map[string]string
LocationOptions []string
ServerOptions []string
ComponentOptions []string
StepURLs map[string]string
ModeURLs map[string]string
}
type stylePlaygroundPageData struct {
Title string
CurrentPath string
Style string
StyleLabel string
StyleURLs map[string]string
StyleClass string
LoadingDemo bool
LoadingDemoURL string
ClearLoadingURL string
}
type operatorToolJob struct {
ID string
Tool string
Scope string
Mode string
Status string
Owner string
StartedAt string
Selected bool
ToggleURL string
RetryURL string
CancelURL string
ExportURL string
InspectURL string
}
type operatorToolsPageData struct {
Title string
CurrentPath string
Scope string
Queue string
Rows []operatorToolJob
VisibleCount int
SelectedCount int
SelectedVisible int
SelectionOutside int
ActionMessage string
ScopeURLs map[string]string
QueueURLs map[string]string
SelectVisibleURL string
ClearVisibleURL string
ClearSelectionURL string
RunSelectedURL string
RetrySelectedURL string
CancelSelectedURL string
OpenReviewModalURL string
ImportPreviewURL string
ExportFilteredURL string
ExportSelectedURL string
SafetyChecklist []string
RecentActivityNotes []string
}
type timelineEvent struct {
ID string
At string
Action string
Source string
Entity string
Target string
Detail string
Slot string
Device string
}
type timelineCard struct {
ID string
Day string
Title string
Action string
Source string
Count int
SummaryLeft []string
SummaryRight []string
Items []timelineEvent
OpenURL string
Open bool
}
type timelinePageData struct {
Title string
CurrentPath string
ActionFilter string
SourceFilter string
ActionURLs map[string]string
SourceURLs map[string]string
Cards []timelineCard
OpenCard *timelineCard
CardSearch string
ClearCardURL string
ActiveEvent *timelineEvent
}
func (s *Server) handleControlsPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/controls" {
http.NotFound(w, r)
return
}
segment := strings.TrimSpace(r.URL.Query().Get("segment"))
if segment == "" {
segment = "all"
}
page := parsePositiveInt(r.URL.Query().Get("page"), 1)
selected := parseIDSet(r.URL.Query()["sel"])
rows := demoControlsRows()
counts := map[string]int{"all": len(rows), "ready": 0, "warning": 0, "review": 0}
filteredAll := make([]controlsRow, 0, len(rows))
filteredIDs := make([]string, 0, len(rows))
for _, row := range rows {
counts[strings.ToLower(row.Status)]++
if segment != "all" && !strings.EqualFold(row.Status, segment) {
continue
}
filteredAll = append(filteredAll, row)
filteredIDs = append(filteredIDs, strconv.Itoa(row.ID))
}
pageRows, pager := paginateControlsRows(segment, selected, filteredAll, page, 5)
visibleIDs := make([]string, 0, len(pageRows))
for _, row := range pageRows {
visibleIDs = append(visibleIDs, strconv.Itoa(row.ID))
}
switch strings.TrimSpace(r.URL.Query().Get("selection_action")) {
case "select_visible":
for _, id := range visibleIDs {
selected[id] = true
}
case "clear_visible":
for _, id := range visibleIDs {
delete(selected, id)
}
case "select_filtered":
for _, id := range filteredIDs {
selected[id] = true
}
case "clear_filtered":
for _, id := range filteredIDs {
delete(selected, id)
}
case "clear_all":
selected = map[string]bool{}
case "toggle":
id := strings.TrimSpace(r.URL.Query().Get("id"))
if id != "" {
if selected[id] {
delete(selected, id)
} else {
selected[id] = true
}
}
}
selectedCSV := joinSelectedIDs(selected)
pageRows, pager = paginateControlsRows(segment, selected, filteredAll, page, 5)
visibleIDs = visibleIDs[:0]
for _, row := range pageRows {
visibleIDs = append(visibleIDs, strconv.Itoa(row.ID))
}
selectedVisible := 0
for i := range pageRows {
id := strconv.Itoa(pageRows[i].ID)
pageRows[i].Selected = selected[id]
if pageRows[i].Selected {
selectedVisible++
}
pageRows[i].ToggleURL = controlsURL(segment, pager.Page, selectedCSV, "toggle", id, "", nil)
pageRows[i].EditURL = modalURL("edit", "edit", selectedCSV, id)
pageRows[i].RemoveURL = modalURL("delete", "confirm", selectedCSV, id)
}
selectedHidden := len(selected) - selectedVisible
if selectedHidden < 0 {
selectedHidden = 0
}
action := strings.TrimSpace(r.URL.Query().Get("bulk"))
loading := r.URL.Query().Get("loading") == "1"
actionMsg := ""
if action != "" {
actionMsg = fmt.Sprintf("Bulk action preview: %s on %d selected item(s).", action, len(selected))
}
data := controlsPageData{
Title: "Controls + Selection Pattern",
CurrentPath: "/patterns/controls",
Rows: pageRows,
Segment: segment,
Page: pager.Page,
Pager: pager,
VisibleCount: len(pageRows),
SelectedCount: len(selected),
SelectedVisible: selectedVisible,
SelectedHidden: selectedHidden,
ActionMessage: actionMsg,
SegmentedCounts: counts,
SegmentURLs: controlsSegmentURLs(selectedCSV),
SelectVisibleURL: controlsURL(segment, pager.Page, selectedCSV, "select_visible", "", "", nil),
SelectFilteredURL: controlsURL(segment, pager.Page, selectedCSV, "select_filtered", "", "", nil),
ClearVisibleURL: controlsURL(segment, pager.Page, selectedCSV, "clear_visible", "", "", nil),
ClearFilteredURL: controlsURL(segment, pager.Page, selectedCSV, "clear_filtered", "", "", nil),
ClearSelectionURL: controlsURL(segment, pager.Page, "", "clear_all", "", "", nil),
BulkReviewURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "review", nil),
BulkArchiveURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "archive", nil),
BulkExportURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "export", nil),
BulkRetrySyncURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "retry_sync", nil),
OpenEditSelectedURL: modalURL("edit", "edit", selectedCSV, ""),
OpenDeleteSelectedURL: modalURL("delete", "confirm", selectedCSV, ""),
HasSelection: len(selected) > 0,
SimulateLoading: loading,
SimulateLoadingURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "review", map[string]string{"loading": "1"}),
ClearLoadingURL: controlsURL(segment, pager.Page, selectedCSV, "", "", "", map[string]string{"loading": ""}),
}
s.renderTemplate(w, "controls_pattern.html", data)
}
func (s *Server) handleModalPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/modals" {
http.NotFound(w, r)
return
}
open := strings.TrimSpace(r.URL.Query().Get("open"))
stage := strings.TrimSpace(r.URL.Query().Get("stage"))
if stage == "" {
stage = "edit"
}
selectedIDs := selectedIDSlice(r.URL.Query()["sel"])
msg := ""
switch stage {
case "confirm":
msg = "Confirm stage: summarize changes and require explicit confirmation."
case "done":
msg = "Completed state: show success summary and next actions."
default:
msg = "Edit stage: collect inputs and validate before transition to confirm."
}
data := modalDemoPageData{
Title: "Modal Workflows Pattern",
CurrentPath: "/patterns/modals",
Open: open,
Stage: stage,
Message: msg,
SelectedIDs: selectedIDs,
}
s.renderTemplate(w, "modal_pattern.html", data)
}
func (s *Server) handleIOPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/io" {
http.NotFound(w, r)
return
}
mode := strings.TrimSpace(r.URL.Query().Get("import_mode"))
if mode == "" {
mode = "preview"
}
fileName := strings.TrimSpace(r.URL.Query().Get("file"))
if fileName == "" {
fileName = "items.csv"
}
scope := strings.TrimSpace(r.URL.Query().Get("scope"))
if scope == "" {
scope = "filtered"
}
format := strings.TrimSpace(r.URL.Query().Get("format"))
if format == "" {
format = "csv"
}
preview := []ioImportPreviewRow{
{RowNo: 1, ItemCode: "CMP-001", Name: "Controller board", Qty: 2, Status: "ok"},
{RowNo: 2, ItemCode: "CMP-002", Name: "PSU module", Qty: 1, Status: "warning"},
{RowNo: 3, ItemCode: "CMP-003", Name: "Network adapter", Qty: 4, Status: "ok"},
{RowNo: 4, ItemCode: "CMP-004", Name: "Missing mapping sample", Qty: 1, Status: "error"},
}
msg := "Import workflow pattern: upload -> preview/validate -> confirm."
if mode == "confirm" {
msg = "Confirm import step: user reviews validation summary before submitting."
}
exportMsg := "Export workflow pattern: explicit format/scope selection and predictable filename."
if r.URL.Query().Get("export_ready") == "1" {
exportMsg = "Export is ready. Use the download action below (real CSV endpoint in demo)."
}
data := ioPageData{
Title: "Import / Export Pattern",
CurrentPath: "/patterns/io",
ImportMode: mode,
FileName: fileName,
ImportMessage: msg,
PreviewRows: preview,
ExportFormat: format,
ExportScope: scope,
ExportMessage: exportMsg,
}
s.renderTemplate(w, "io_pattern.html", data)
}
func (s *Server) handleFormsPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/forms" {
http.NotFound(w, r)
return
}
mode := strings.TrimSpace(r.URL.Query().Get("mode"))
if mode == "" {
mode = "register"
}
step := strings.TrimSpace(r.URL.Query().Get("step"))
if step == "" {
step = "edit"
}
data := formsDemoPageData{
Title: "Forms + Validation Pattern",
CurrentPath: "/patterns/forms",
Mode: mode,
Step: step,
ServerSerial: strings.TrimSpace(r.URL.Query().Get("server_serial")),
Location: strings.TrimSpace(r.URL.Query().Get("location")),
ComponentSerial: strings.TrimSpace(r.URL.Query().Get("component_serial")),
EventDate: strings.TrimSpace(r.URL.Query().Get("event_date")),
Details: strings.TrimSpace(r.URL.Query().Get("details")),
FieldErrors: map[string]string{},
LocationOptions: []string{"AOC#1", "AOC#2", "PSU#1", "PSU#2", "CTRL#1"},
ServerOptions: []string{"SRV-001", "SRV-002", "SRV-003", "SRV-010"},
ComponentOptions: []string{
"NIC-AX210-001", "NIC-AX210-002", "PSU-750W-100", "CTRL-MGMT-014",
},
StepURLs: map[string]string{
"edit": formsURL(mode, "edit", url.Values{}),
"review": formsURL(mode, "review", carryFormFields(r.URL.Query())),
"confirm": formsURL(mode, "confirm", carryFormFields(r.URL.Query())),
},
ModeURLs: map[string]string{
"register": formsURL("register", "edit", carryFormFields(r.URL.Query())),
"import": formsURL("import", "edit", carryFormFields(r.URL.Query())),
},
}
if data.EventDate == "" {
data.EventDate = "2026-02-23"
}
if step == "review" || step == "confirm" {
validateFormsDemo(&data)
if len(data.FieldErrors) > 0 && step != "edit" {
data.Message = "Validation errors must be resolved before confirmation."
}
}
if step == "edit" {
data.Message = "Edit step: enter values, use suggestions, then move to review."
} else if step == "review" {
if len(data.FieldErrors) == 0 {
data.Message = "Review step: summarize recognized values and request explicit confirmation."
}
} else if step == "confirm" {
if len(data.FieldErrors) == 0 {
data.Message = "Done state: show human-readable result and next actions."
}
}
s.renderTemplate(w, "forms_pattern.html", data)
}
func (s *Server) handleStylePlaygroundPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/style-playground" {
http.NotFound(w, r)
return
}
style := strings.TrimSpace(r.URL.Query().Get("style"))
if style == "" {
style = "aqua"
}
if style == "vaporwave" {
style = "vaporwave-soft"
}
allowed := map[string]string{
"linen": "Linen / Editorial",
"slate": "Slate / Utility",
"signal": "Signal / Accent",
"y2k-silver": "Y2K / Silver Chrome",
"vaporwave-soft": "Vaporwave / Soft Day",
"vaporwave-night": "Vaporwave / Night",
"aqua": "macOS Aqua",
"win9x": "Windows 95-2000",
}
label, ok := allowed[style]
if !ok {
style = "aqua"
label = allowed[style]
}
styleClass := "theme-" + style
if style == "vaporwave-soft" {
// Keep CSS compatibility with the first vaporwave implementation.
styleClass = "theme-vaporwave"
}
data := stylePlaygroundPageData{
Title: "Style Playground",
CurrentPath: "/patterns/style-playground",
Style: style,
StyleLabel: label,
StyleClass: styleClass,
StyleURLs: map[string]string{
"linen": anchored("/patterns/style-playground?style=linen", "style-presets"),
"slate": anchored("/patterns/style-playground?style=slate", "style-presets"),
"signal": anchored("/patterns/style-playground?style=signal", "style-presets"),
"y2k-silver": anchored("/patterns/style-playground?style=y2k-silver", "style-presets"),
"vaporwave-soft": anchored("/patterns/style-playground?style=vaporwave-soft", "style-presets"),
"vaporwave-night": anchored("/patterns/style-playground?style=vaporwave-night", "style-presets"),
"aqua": anchored("/patterns/style-playground?style=aqua", "style-presets"),
"win9x": anchored("/patterns/style-playground?style=win9x", "style-presets"),
},
LoadingDemo: r.URL.Query().Get("loading") == "1",
LoadingDemoURL: anchored("/patterns/style-playground?style="+style+"&loading=1", "style-components"),
ClearLoadingURL: anchored("/patterns/style-playground?style="+style, "style-components"),
}
s.renderTemplate(w, "style_playground_pattern.html", data)
}
func (s *Server) handleOperatorToolsPattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/operator-tools" {
http.NotFound(w, r)
return
}
scope := strings.TrimSpace(r.URL.Query().Get("scope"))
if scope == "" {
scope = "assets"
}
queue := strings.TrimSpace(r.URL.Query().Get("queue"))
if queue == "" {
queue = "all"
}
selected := parseIDSet(r.URL.Query()["sel"])
all := demoOperatorToolJobs()
filtered := make([]operatorToolJob, 0, len(all))
visibleIDs := make([]string, 0, len(all))
for _, row := range all {
if scope != "all" && !strings.EqualFold(row.Scope, scope) {
continue
}
if queue != "all" && !strings.EqualFold(row.Status, queue) {
continue
}
filtered = append(filtered, row)
visibleIDs = append(visibleIDs, row.ID)
}
switch strings.TrimSpace(r.URL.Query().Get("selection_action")) {
case "select_visible":
for _, id := range visibleIDs {
selected[id] = true
}
case "clear_visible":
for _, id := range visibleIDs {
delete(selected, id)
}
case "clear_all":
selected = map[string]bool{}
case "toggle":
id := strings.TrimSpace(r.URL.Query().Get("id"))
if id != "" {
if selected[id] {
delete(selected, id)
} else {
selected[id] = true
}
}
}
selCSV := joinSelectedIDs(selected)
selectedVisible := 0
for i := range filtered {
filtered[i].Selected = selected[filtered[i].ID]
if filtered[i].Selected {
selectedVisible++
}
filtered[i].ToggleURL = operatorToolsURL(scope, queue, selCSV, "toggle", filtered[i].ID, "")
filtered[i].RetryURL = operatorToolsURL(scope, queue, selCSV, "", "", "retry")
filtered[i].CancelURL = operatorToolsURL(scope, queue, selCSV, "", "", "cancel")
filtered[i].ExportURL = anchored("/patterns/io?scope=filtered&export_ready=1", "io-export")
filtered[i].InspectURL = timelineURL("", "", "c1", "", "")
}
selectedHidden := len(selected) - selectedVisible
if selectedHidden < 0 {
selectedHidden = 0
}
action := strings.TrimSpace(r.URL.Query().Get("batch"))
actionMessage := ""
if action != "" {
actionMessage = fmt.Sprintf("Operator batch preview: %s on %d selected job(s).", action, len(selected))
}
if len(selected) == 0 && action != "" {
actionMessage = "Operator batch preview requires explicit selection first."
}
data := operatorToolsPageData{
Title: "Operator Tools Pattern",
CurrentPath: "/patterns/operator-tools",
Scope: scope,
Queue: queue,
Rows: filtered,
VisibleCount: len(filtered),
SelectedCount: len(selected),
SelectedVisible: selectedVisible,
SelectionOutside: selectedHidden,
ActionMessage: actionMessage,
ScopeURLs: map[string]string{
"assets": operatorToolsURL("assets", queue, selCSV, "", "", ""),
"components": operatorToolsURL("components", queue, selCSV, "", "", ""),
"imports": operatorToolsURL("imports", queue, selCSV, "", "", ""),
"maintenance": operatorToolsURL("maintenance", queue, selCSV, "", "", ""),
"all": operatorToolsURL("all", queue, selCSV, "", "", ""),
},
QueueURLs: map[string]string{
"all": operatorToolsURL(scope, "all", selCSV, "", "", ""),
"queued": operatorToolsURL(scope, "queued", selCSV, "", "", ""),
"running": operatorToolsURL(scope, "running", selCSV, "", "", ""),
"failed": operatorToolsURL(scope, "failed", selCSV, "", "", ""),
"done": operatorToolsURL(scope, "done", selCSV, "", "", ""),
},
SelectVisibleURL: operatorToolsURL(scope, queue, selCSV, "select_visible", "", ""),
ClearVisibleURL: operatorToolsURL(scope, queue, selCSV, "clear_visible", "", ""),
ClearSelectionURL: operatorToolsURL(scope, queue, "", "clear_all", "", ""),
RunSelectedURL: operatorToolsURL(scope, queue, selCSV, "", "", "run"),
RetrySelectedURL: operatorToolsURL(scope, queue, selCSV, "", "", "retry"),
CancelSelectedURL: operatorToolsURL(scope, queue, selCSV, "", "", "cancel"),
OpenReviewModalURL: modalURL("edit", "confirm", selCSV, ""),
ImportPreviewURL: anchored("/patterns/io?import_mode=preview&file=operator-batch.csv", "io-import"),
ExportFilteredURL: anchored("/patterns/io?scope=filtered&format=csv&export_ready=1", "io-export"),
ExportSelectedURL: anchored("/patterns/io?scope=selected&format=csv&export_ready=1", "io-export"),
SafetyChecklist: []string{
"Require explicit selection for batch actions (never infer from hidden defaults).",
"Show scope and queue filters in the action bar before executing.",
"Destructive or high-impact operations must route through a confirm step.",
"Batch result summaries must be exportable and human-readable.",
},
RecentActivityNotes: []string{
"Failed runs should stay filterable and retryable without losing scope context.",
"Import and export affordances belong near batch controls, not hidden in settings.",
"Queue status labels must be stable and reused across table rows and summaries.",
},
}
s.renderTemplate(w, "operator_tools_pattern.html", data)
}
func (s *Server) handleIOExportCSV(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/io/export.csv" {
http.NotFound(w, r)
return
}
scope := r.URL.Query().Get("scope")
if scope == "" {
scope = "filtered"
}
filename := "2026-02-23 (DEMO) items.csv"
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
_, _ = w.Write([]byte{0xEF, 0xBB, 0xBF})
var buf bytes.Buffer
cw := csv.NewWriter(&buf)
cw.Comma = ';'
_ = cw.Write([]string{"Code", "Name", "Category", "Status", "Qty"})
for _, row := range demoControlsRows() {
if scope == "selected" && row.ID%2 == 0 {
continue
}
_ = cw.Write([]string{
fmt.Sprintf("CMP-%03d", row.ID),
row.Name,
row.Type,
row.Status,
strconv.Itoa((row.ID % 5) + 1),
})
}
cw.Flush()
_, _ = w.Write(buf.Bytes())
}
func (s *Server) handleTimelinePattern(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/patterns/timeline" {
http.NotFound(w, r)
return
}
actionFilter := strings.TrimSpace(r.URL.Query().Get("action"))
sourceFilter := strings.TrimSpace(r.URL.Query().Get("source"))
openID := strings.TrimSpace(r.URL.Query().Get("open"))
cardSearch := strings.TrimSpace(r.URL.Query().Get("q"))
activeEventID := strings.TrimSpace(r.URL.Query().Get("event"))
all := demoTimelineCards()
cards := make([]timelineCard, 0, len(all))
for _, c := range all {
if actionFilter != "" && !strings.EqualFold(c.Action, actionFilter) {
continue
}
if sourceFilter != "" && !strings.EqualFold(c.Source, sourceFilter) {
continue
}
c.OpenURL = timelineURL(actionFilter, sourceFilter, c.ID, "", "")
c.Open = c.ID == openID
if c.Open && cardSearch != "" {
needle := strings.ToLower(cardSearch)
items := make([]timelineEvent, 0, len(c.Items))
for _, it := range c.Items {
h := strings.ToLower(strings.Join([]string{it.Action, it.Source, it.Entity, it.Target, it.Slot, it.Device, it.Detail}, " "))
if strings.Contains(h, needle) {
items = append(items, it)
}
}
c.Items = items
c.Count = len(items)
}
cards = append(cards, c)
}
var openCard *timelineCard
var activeEvent *timelineEvent
for i := range cards {
if cards[i].ID != openID {
continue
}
openCard = &cards[i]
if len(cards[i].Items) == 0 {
break
}
idx := 0
if activeEventID != "" {
for j := range cards[i].Items {
if cards[i].Items[j].ID == activeEventID {
idx = j
break
}
}
}
activeEvent = &cards[i].Items[idx]
break
}
data := timelinePageData{
Title: "Timeline Cards Pattern",
CurrentPath: "/patterns/timeline",
ActionFilter: actionFilter,
SourceFilter: sourceFilter,
ActionURLs: map[string]string{
"": timelineURL("", sourceFilter, "", "", ""),
"installation": timelineURL("installation", sourceFilter, "", "", ""),
"removal": timelineURL("removal", sourceFilter, "", "", ""),
"edit": timelineURL("edit", sourceFilter, "", "", ""),
},
SourceURLs: map[string]string{
"": timelineURL(actionFilter, "", "", "", ""),
"manual": timelineURL(actionFilter, "manual", "", "", ""),
"ingest": timelineURL(actionFilter, "ingest", "", "", ""),
"system": timelineURL(actionFilter, "system", "", "", ""),
},
Cards: cards,
OpenCard: openCard,
CardSearch: cardSearch,
ClearCardURL: timelineURL(actionFilter, sourceFilter, "", "", ""),
ActiveEvent: activeEvent,
}
s.renderTemplate(w, "timeline_pattern.html", data)
}
func validateFormsDemo(d *formsDemoPageData) {
if d.ServerSerial == "" && d.Mode == "register" {
d.FieldErrors["server_serial"] = "Server serial is required in register mode."
}
if d.ComponentSerial == "" {
d.FieldErrors["component_serial"] = "Component serial is required."
}
if d.EventDate == "" {
d.FieldErrors["event_date"] = "Date is required."
}
if d.Location == "" && d.Mode == "register" {
d.FieldErrors["location"] = "Location/slot is required."
}
}
func (s *Server) renderTemplate(w http.ResponseWriter, name string, data any) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, name, data); err != nil {
http.Error(w, "template error", http.StatusInternalServerError)
}
}
func parseIDSet(values []string) map[string]bool {
out := map[string]bool{}
for _, v := range values {
for _, p := range strings.Split(v, ",") {
p = strings.TrimSpace(p)
if p == "" {
continue
}
out[p] = true
}
}
return out
}
func selectedIDSlice(values []string) []string {
set := parseIDSet(values)
keys := make([]string, 0, len(set))
for k := range set {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
ai, aerr := strconv.Atoi(keys[i])
aj, berr := strconv.Atoi(keys[j])
if aerr == nil && berr == nil {
return ai < aj
}
return keys[i] < keys[j]
})
return keys
}
func joinSelectedIDs(set map[string]bool) string {
if len(set) == 0 {
return ""
}
keys := make([]string, 0, len(set))
for k := range set {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
ai, aerr := strconv.Atoi(keys[i])
aj, berr := strconv.Atoi(keys[j])
if aerr == nil && berr == nil {
return ai < aj
}
return keys[i] < keys[j]
})
return strings.Join(keys, ",")
}
func demoControlsRows() []controlsRow {
names := []string{
"Controller board", "Power module", "NIC adapter", "Drive tray", "Cooling fan", "BMC board",
"CPU module", "Memory kit", "Rail set", "Backplane", "Patch panel", "Optics cage",
}
types := []string{"Compute", "Power", "Networking", "Storage"}
statuses := []string{"ready", "warning", "review"}
rows := make([]controlsRow, 0, len(names))
for i, n := range names {
rows = append(rows, controlsRow{
ID: i + 1,
Name: n,
Type: types[i%len(types)],
Status: statuses[i%len(statuses)],
})
}
return rows
}
func controlsSegmentURLs(selCSV string) map[string]string {
return map[string]string{
"all": controlsURL("all", 1, selCSV, "", "", "", nil),
"ready": controlsURL("ready", 1, selCSV, "", "", "", nil),
"warning": controlsURL("warning", 1, selCSV, "", "", "", nil),
"review": controlsURL("review", 1, selCSV, "", "", "", nil),
}
}
func carryFormFields(q url.Values) url.Values {
out := url.Values{}
for _, k := range []string{"server_serial", "location", "component_serial", "event_date", "details"} {
if v := strings.TrimSpace(q.Get(k)); v != "" {
out.Set(k, v)
}
}
return out
}
func formsURL(mode, step string, extra url.Values) string {
q := url.Values{}
if mode != "" {
q.Set("mode", mode)
}
if step != "" {
q.Set("step", step)
}
for k, vals := range extra {
for _, v := range vals {
q.Add(k, v)
}
}
if qs := q.Encode(); qs != "" {
return anchored("/patterns/forms?"+qs, "forms-contract")
}
return anchored("/patterns/forms", "forms-contract")
}
func paginateControlsRows(segment string, selected map[string]bool, rows []controlsRow, page, perPage int) ([]controlsRow, tableDemoPager) {
totalItems := len(rows)
totalPages := 1
if totalItems > 0 {
totalPages = (totalItems + perPage - 1) / perPage
}
if page < 1 {
page = 1
}
if page > totalPages {
page = totalPages
}
start := 0
end := 0
from := 0
to := 0
pageRows := []controlsRow{}
if totalItems > 0 {
start = (page - 1) * perPage
end = start + perPage
if end > totalItems {
end = totalItems
}
from = start + 1
to = end
pageRows = append(pageRows, rows[start:end]...)
}
selCSV := joinSelectedIDs(selected)
links := buildControlsPageLinks(segment, selCSV, page, totalPages)
pager := tableDemoPager{
Page: page,
PerPage: perPage,
TotalItems: totalItems,
TotalPages: totalPages,
From: from,
To: to,
Links: links,
}
if page > 1 {
pager.PrevURL = controlsURL(segment, page-1, selCSV, "", "", "", nil)
}
if page < totalPages {
pager.NextURL = controlsURL(segment, page+1, selCSV, "", "", "", nil)
}
return pageRows, pager
}
func buildControlsPageLinks(segment, selCSV string, current, totalPages int) []tableDemoPageLink {
if totalPages <= 1 {
return nil
}
if totalPages <= 4 {
out := make([]tableDemoPageLink, 0, totalPages)
for p := 1; p <= totalPages; p++ {
out = append(out, tableDemoPageLink{
Label: strconv.Itoa(p),
URL: controlsURL(segment, p, selCSV, "", "", "", nil),
Current: p == current,
})
}
return out
}
pages := map[int]bool{
1: true, 2: true,
totalPages - 1: true, totalPages: true,
current - 1: true, current: true, current + 1: true,
}
keys := make([]int, 0, len(pages))
for p := range pages {
if p < 1 || p > totalPages {
continue
}
keys = append(keys, p)
}
sort.Ints(keys)
out := make([]tableDemoPageLink, 0, len(keys)+2)
prev := 0
for _, p := range keys {
if prev != 0 && p-prev > 1 {
out = append(out, tableDemoPageLink{Label: "...", Ellipsis: true})
}
out = append(out, tableDemoPageLink{
Label: strconv.Itoa(p),
URL: controlsURL(segment, p, selCSV, "", "", "", nil),
Current: p == current,
})
prev = p
}
return out
}
func controlsURL(segment string, page int, selCSV, selectionAction, id, bulk string, extra map[string]string) string {
q := url.Values{}
if segment != "" {
q.Set("segment", segment)
}
if page > 1 {
q.Set("page", strconv.Itoa(page))
}
if selCSV != "" {
q.Set("sel", selCSV)
}
if selectionAction != "" {
q.Set("selection_action", selectionAction)
}
if id != "" {
q.Set("id", id)
}
if bulk != "" {
q.Set("bulk", bulk)
}
for k, v := range extra {
if v == "" {
q.Del(k)
continue
}
q.Set(k, v)
}
if qs := q.Encode(); qs != "" {
return anchored("/patterns/controls?"+qs, "controls-selection")
}
return anchored("/patterns/controls", "controls-selection")
}
func operatorToolsURL(scope, queue, selCSV, selectionAction, id, batch string) string {
q := url.Values{}
if scope != "" {
q.Set("scope", scope)
}
if queue != "" {
q.Set("queue", queue)
}
if selCSV != "" {
q.Set("sel", selCSV)
}
if selectionAction != "" {
q.Set("selection_action", selectionAction)
}
if id != "" {
q.Set("id", id)
}
if batch != "" {
q.Set("batch", batch)
}
if qs := q.Encode(); qs != "" {
return anchored("/patterns/operator-tools?"+qs, "operator-queue")
}
return anchored("/patterns/operator-tools", "operator-queue")
}
func modalURL(open, stage, selCSV, singleID string) string {
q := url.Values{}
if open != "" {
q.Set("open", open)
}
if stage != "" {
q.Set("stage", stage)
}
if singleID != "" {
q.Set("sel", singleID)
} else if selCSV != "" {
q.Set("sel", selCSV)
}
if qs := q.Encode(); qs != "" {
return anchored("/patterns/modals?"+qs, "modal-open-states")
}
return anchored("/patterns/modals", "modal-open-states")
}
func timelineURL(action, source, open, qtext, event string) string {
q := url.Values{}
if action != "" {
q.Set("action", action)
}
if source != "" {
q.Set("source", source)
}
if open != "" {
q.Set("open", open)
}
if qtext != "" {
q.Set("q", qtext)
}
if event != "" {
q.Set("event", event)
}
fragment := "timeline-filters"
if open != "" {
fragment = "timeline-drilldown"
}
if qs := q.Encode(); qs != "" {
return anchored("/patterns/timeline?"+qs, fragment)
}
return anchored("/patterns/timeline", fragment)
}
func anchored(path, fragment string) string {
if fragment == "" {
return path
}
if strings.Contains(path, "#") {
return path
}
return path + "#" + fragment
}
func demoTimelineCards() []timelineCard {
return []timelineCard{
{
ID: "c1",
Day: "2026-02-23",
Title: "Installed 3 components",
Action: "installation",
Source: "manual",
Count: 3,
SummaryLeft: []string{"2x Network adapter", "1x Controller board"},
SummaryRight: []string{"AOC#1", "AOC#2", "CTRL#1"},
Items: []timelineEvent{
{ID: "e1", At: "10:12", Action: "installation", Source: "manual", Entity: "Asset A-12", Target: "Network adapter", Slot: "AOC#1", Device: "NIC", Detail: "Installed after maintenance"},
{ID: "e2", At: "10:13", Action: "installation", Source: "manual", Entity: "Asset A-12", Target: "Network adapter", Slot: "AOC#2", Device: "NIC", Detail: "Installed after maintenance"},
{ID: "e3", At: "10:16", Action: "installation", Source: "manual", Entity: "Asset A-12", Target: "Controller board", Slot: "CTRL#1", Device: "Controller", Detail: "Replacement unit attached"},
},
},
{
ID: "c2",
Day: "2026-02-23",
Title: "Removed 2 components",
Action: "removal",
Source: "ingest",
Count: 2,
SummaryLeft: []string{"1x PSU module", "1x Cooling fan"},
SummaryRight: []string{"PSU#2", "FAN#4"},
Items: []timelineEvent{
{ID: "e4", At: "08:42", Action: "removal", Source: "ingest", Entity: "Asset B-07", Target: "PSU module", Slot: "PSU#2", Device: "Power", Detail: "Absent in latest snapshot"},
{ID: "e5", At: "08:42", Action: "removal", Source: "ingest", Entity: "Asset B-07", Target: "Cooling fan", Slot: "FAN#4", Device: "Cooling", Detail: "Absent in latest snapshot"},
},
},
{
ID: "c3",
Day: "2026-02-22",
Title: "Bulk metadata edit",
Action: "edit",
Source: "system",
Count: 4,
SummaryLeft: []string{"Vendor normalized", "Status recomputed"},
SummaryRight: []string{"4 affected rows"},
Items: []timelineEvent{
{ID: "e6", At: "21:11", Action: "edit", Source: "system", Entity: "Asset C-03", Target: "Component metadata", Slot: "AOC#3", Device: "NIC", Detail: "Vendor name normalized"},
{ID: "e7", At: "21:11", Action: "edit", Source: "system", Entity: "Asset C-03", Target: "Component metadata", Slot: "AOC#4", Device: "NIC", Detail: "Vendor name normalized"},
{ID: "e8", At: "21:12", Action: "edit", Source: "system", Entity: "Asset C-03", Target: "Status", Slot: "PSU#1", Device: "Power", Detail: "Status recalculated from observations"},
{ID: "e9", At: "21:12", Action: "edit", Source: "system", Entity: "Asset C-03", Target: "Status", Slot: "PSU#2", Device: "Power", Detail: "Status recalculated from observations"},
},
},
}
}
func demoOperatorToolJobs() []operatorToolJob {
return []operatorToolJob{
{ID: "job-101", Tool: "Recompute status", Scope: "assets", Mode: "dry-run", Status: "queued", Owner: "operator", StartedAt: "2026-02-23 10:20"},
{ID: "job-102", Tool: "Bulk assign labels", Scope: "components", Mode: "apply", Status: "running", Owner: "operator", StartedAt: "2026-02-23 10:16"},
{ID: "job-103", Tool: "Import snapshot preview", Scope: "imports", Mode: "preview", Status: "done", Owner: "operator", StartedAt: "2026-02-23 10:04"},
{ID: "job-104", Tool: "Repair mappings", Scope: "components", Mode: "apply", Status: "failed", Owner: "operator", StartedAt: "2026-02-23 09:58"},
{ID: "job-105", Tool: "Refresh timelines", Scope: "maintenance", Mode: "apply", Status: "queued", Owner: "scheduler", StartedAt: "2026-02-23 09:40"},
{ID: "job-106", Tool: "Export filtered report", Scope: "assets", Mode: "export", Status: "done", Owner: "operator", StartedAt: "2026-02-23 09:22"},
{ID: "job-107", Tool: "Validate import mapping", Scope: "imports", Mode: "preview", Status: "running", Owner: "operator", StartedAt: "2026-02-23 09:10"},
{ID: "job-108", Tool: "Cleanup stale drafts", Scope: "maintenance", Mode: "apply", Status: "failed", Owner: "system", StartedAt: "2026-02-23 08:55"},
}
}
func withQuery(base string, updates map[string]string) string {
u, err := url.Parse(base)
if err != nil {
return base
}
q := u.Query()
for k, v := range updates {
if v == "" {
q.Del(k)
continue
}
q.Set(k, v)
}
u.RawQuery = q.Encode()
return u.String()
}
func dict(args ...string) map[string]string {
out := map[string]string{}
for i := 0; i+1 < len(args); i += 2 {
out[args[i]] = args[i+1]
}
return out
}