222 lines
5.7 KiB
Go
222 lines
5.7 KiB
Go
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()
|
|
}
|
|
|
|
// 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()
|
|
}
|