package server import ( "bytes" "html/template" "strings" "github.com/dal/kaos/internal/supervisor" "github.com/dal/kaos/web" ) // taskCardData wraps a task with UI display info. type taskCardData struct { supervisor.Task CanRun bool Action string // blocked, review, run, running, question, approve, done } // columnData holds data for rendering a single kanban column. type columnData struct { Name string Label string Icon string Count int Tasks []taskCardData } // dashboardData holds data for the full dashboard page. type dashboardData struct { Columns []columnData } // taskDetailData holds data for the task detail panel. type taskDetailData struct { Task supervisor.Task Content template.HTML HasReport bool } // statusIcons maps folder names to emoji icons. var statusIcons = map[string]string{ "backlog": "📦", "ready": "📋", "active": "🔄", "review": "👀", "done": "✅", } // columnOrder defines the display order of columns. var columnOrder = []string{"backlog", "ready", "active", "review", "done"} // templateFuncs provides custom functions for templates. var templateFuncs = template.FuncMap{ "joinDeps": func(deps []string) string { return strings.Join(deps, ", ") }, } // templates holds the parsed template set. var templates *template.Template func init() { templates = template.Must( template.New("").Funcs(templateFuncs).ParseFS( web.TemplatesFS, "templates/layout.html", "templates/dashboard.html", "templates/docs-list.html", "templates/docs-view.html", "templates/console.html", "templates/submit.html", "templates/partials/column.html", "templates/partials/task-card.html", "templates/partials/task-detail.html", "templates/partials/search-results.html", ), ) } // renderDashboard generates the full dashboard HTML page. func renderDashboard(columns map[string][]supervisor.Task) string { // Build set of done task IDs for dependency checking doneSet := make(map[string]bool) for _, t := range columns["done"] { doneSet[t.ID] = true } data := buildDashboardData(columns, doneSet) var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "layout.html", data); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // buildDashboardData creates dashboard data with task actions resolved. func buildDashboardData(columns map[string][]supervisor.Task, doneSet map[string]bool) dashboardData { data := dashboardData{} for _, col := range columnOrder { tasks := columns[col] cards := make([]taskCardData, len(tasks)) for i, t := range tasks { action := resolveTaskAction(t, doneSet) cards[i] = taskCardData{ Task: t, CanRun: action == "run", Action: action, } } data.Columns = append(data.Columns, columnData{ Name: col, Label: strings.ToUpper(col), Icon: statusIcons[col], Count: len(tasks), Tasks: cards, }) } return data } // renderBoardFragment generates only the board HTML for SSE updates. func renderBoardFragment(columns map[string][]supervisor.Task) string { doneSet := make(map[string]bool) for _, t := range columns["done"] { doneSet[t.ID] = true } data := buildDashboardData(columns, doneSet) var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "content", data); err != nil { return "" } return buf.String() } // resolveTaskAction determines which action button to show for a task. func resolveTaskAction(t supervisor.Task, doneSet map[string]bool) string { switch t.Status { case "backlog": for _, dep := range t.DependsOn { if !doneSet[dep] { return "blocked" } } return "review" // deps met, needs operator review before ready case "ready": return "run" case "active": return "running" case "review": return "approve" case "done": return "done" default: return "" } } // renderDocsList generates the docs listing HTML page. func renderDocsList(data docsListData) string { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "docs-list", data); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // renderDocsView generates the docs view HTML page. func renderDocsView(data docsViewData) string { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "docs-view", data); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // renderConsolePage generates the console HTML page. func renderConsolePage() string { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "console", nil); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // renderSubmitPage generates the submit page HTML. func renderSubmitPage() string { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "submit", nil); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // renderSearchResults generates the search results HTML fragment. func renderSearchResults(data searchResultsData) string { var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "search-results", data); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() } // renderReportModal generates HTML fragment for the report overlay modal. func renderReportModal(taskID, title, content string) string { rendered := renderMarkdown([]byte(content), taskID+"-report.md") return `
✕

` + template.HTMLEscapeString(title) + `

` + rendered + `
` } // renderTaskDetail generates HTML fragment for task detail panel. // Content is rendered from markdown to HTML using goldmark. func renderTaskDetail(t supervisor.Task, content string, hasReport bool) string { rendered := renderMarkdown([]byte(content), t.ID+".md") data := taskDetailData{ Task: t, Content: template.HTML(rendered), HasReport: hasReport, } var buf bytes.Buffer if err := templates.ExecuteTemplate(&buf, "task-detail", data); err != nil { return "Greška pri renderovanju: " + err.Error() } return buf.String() }