webui_home.go

  1package web
  2
  3import (
  4	"html/template"
  5	"math"
  6	"net/http"
  7	"sort"
  8	"strconv"
  9	"strings"
 10	"time"
 11
 12	"github.com/charmbracelet/log/v2"
 13	"github.com/charmbracelet/soft-serve/pkg/access"
 14	"github.com/charmbracelet/soft-serve/pkg/backend"
 15	"github.com/charmbracelet/soft-serve/pkg/config"
 16	"github.com/charmbracelet/soft-serve/pkg/proto"
 17	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 18)
 19
 20type HomeRepository struct {
 21	Name        string
 22	ProjectName string
 23	Description string
 24	IsPrivate   bool
 25	CloneURL    string
 26	UpdatedAt   time.Time
 27}
 28
 29type HomeData struct {
 30	BaseData
 31	PaginationData
 32	Repo         proto.Repository
 33	Repositories []HomeRepository
 34	ReadmeHTML   template.HTML
 35}
 36
 37type repoItem struct {
 38	repo       proto.Repository
 39	lastUpdate *time.Time
 40}
 41
 42func home(w http.ResponseWriter, r *http.Request) {
 43	ctx := r.Context()
 44	logger := log.FromContext(ctx)
 45	cfg := config.FromContext(ctx)
 46	be := backend.FromContext(ctx)
 47
 48	if be == nil {
 49		logger.Debug("backend not found in context")
 50		renderInternalServerError(w, r)
 51		return
 52	}
 53
 54	repos, err := be.Repositories(ctx)
 55	if err != nil {
 56		logger.Debug("failed to get repositories", "err", err)
 57		renderInternalServerError(w, r)
 58		return
 59	}
 60
 61	var readmeHTML template.HTML
 62	homeRepos := make([]HomeRepository, 0)
 63	items := make([]repoItem, 0)
 64
 65	readmeHTML, err = getServerReadme(ctx, be)
 66	if err != nil {
 67		logger.Debug("failed to get server README", "err", err)
 68	}
 69
 70	for _, r := range repos {
 71		if r.IsHidden() {
 72			continue
 73		}
 74
 75		al := be.AccessLevelByPublicKey(ctx, r.Name(), nil)
 76		if al >= access.ReadOnlyAccess {
 77			var lastUpdate *time.Time
 78			lu := r.UpdatedAt()
 79			if !lu.IsZero() {
 80				lastUpdate = &lu
 81			}
 82			items = append(items, repoItem{
 83				repo:       r,
 84				lastUpdate: lastUpdate,
 85			})
 86		}
 87	}
 88
 89	sort.Slice(items, func(i, j int) bool {
 90		if items[i].lastUpdate == nil && items[j].lastUpdate != nil {
 91			return false
 92		}
 93		if items[i].lastUpdate != nil && items[j].lastUpdate == nil {
 94			return true
 95		}
 96		if items[i].lastUpdate == nil && items[j].lastUpdate == nil {
 97			return items[i].repo.Name() < items[j].repo.Name()
 98		}
 99		return items[i].lastUpdate.After(*items[j].lastUpdate)
100	})
101
102	page := 1
103	if pageStr := r.URL.Query().Get("page"); pageStr != "" {
104		if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
105			page = p
106		}
107	}
108
109	totalRepos := len(items)
110	totalPages := int(math.Ceil(float64(totalRepos) / float64(defaultReposPerPage)))
111	if totalPages < 1 {
112		totalPages = 1
113	}
114
115	if page > totalPages {
116		page = totalPages
117	}
118	if page < 1 {
119		page = 1
120	}
121
122	startIdx := (page - 1) * defaultReposPerPage
123	endIdx := startIdx + defaultReposPerPage
124	if endIdx > totalRepos {
125		endIdx = totalRepos
126	}
127
128	paginatedItems := items[startIdx:endIdx]
129
130	for _, item := range paginatedItems {
131		repo := item.repo
132		name := repo.Name()
133		projectName := repo.ProjectName()
134		description := strings.TrimSpace(repo.Description())
135		cloneURL := common.RepoURL(cfg.SSH.PublicURL, name)
136
137		var updatedAt time.Time
138		if item.lastUpdate != nil {
139			updatedAt = item.lastUpdate.UTC()
140		}
141
142		homeRepos = append(homeRepos, HomeRepository{
143			Name:        name,
144			ProjectName: projectName,
145			Description: description,
146			IsPrivate:   repo.IsPrivate(),
147			CloneURL:    cloneURL,
148			UpdatedAt:   updatedAt,
149		})
150	}
151
152	data := HomeData{
153		BaseData: BaseData{
154			ServerName: cfg.Name,
155			ActiveTab:  "repositories",
156			Title:      cfg.Name,
157		},
158		PaginationData: PaginationData{
159			Page:        page,
160			TotalPages:  totalPages,
161			HasPrevPage: page > 1,
162			HasNextPage: page < totalPages,
163		},
164		Repositories: homeRepos,
165		ReadmeHTML:   readmeHTML,
166	}
167
168	renderHTML(w, "home.html", data)
169}