projects: add /all endpoint for unlimited project list

Solve pagination issue where configs reference projects not in the
paginated list (default 10 items, but there could be 50+ projects).

Changes:
- Add GET /api/projects/all endpoint that returns ALL projects without
  pagination as simple {uuid, name} objects
- Update frontend loadProjectsForConfigUI() to use /api/projects/all
  instead of /api/projects?status=all
- Ensures all projects are available in projectNameByUUID for config
  display, regardless of total project count

This fixes cases where project names don't display in /configs page
for configs that reference projects outside the paginated range.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-02-09 11:19:49 +03:00
parent e8d0e28415
commit 29edd73744
2 changed files with 36 additions and 4 deletions

View File

@@ -1166,7 +1166,8 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
search := strings.ToLower(strings.TrimSpace(c.Query("search"))) search := strings.ToLower(strings.TrimSpace(c.Query("search")))
author := strings.ToLower(strings.TrimSpace(c.Query("author"))) author := strings.ToLower(strings.TrimSpace(c.Query("author")))
page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "10")) // Return all projects by default (set high limit for configs to reference)
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "1000"))
sortField := strings.ToLower(strings.TrimSpace(c.DefaultQuery("sort", "created_at"))) sortField := strings.ToLower(strings.TrimSpace(c.DefaultQuery("sort", "created_at")))
sortDir := strings.ToLower(strings.TrimSpace(c.DefaultQuery("dir", "desc"))) sortDir := strings.ToLower(strings.TrimSpace(c.DefaultQuery("dir", "desc")))
if status != "active" && status != "archived" && status != "all" { if status != "active" && status != "archived" && status != "all" {
@@ -1318,6 +1319,32 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
}) })
}) })
// GET /api/projects/all - Returns all projects without pagination for UI dropdowns
projects.GET("/all", func(c *gin.Context) {
allProjects, err := projectService.ListByUser(dbUsername, true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Return simplified list of all projects (UUID + Name only)
type ProjectSimple struct {
UUID string `json:"uuid"`
Name string `json:"name"`
}
simplified := make([]ProjectSimple, 0, len(allProjects))
for _, p := range allProjects {
simplified = append(simplified, ProjectSimple{
UUID: p.UUID,
Name: p.Name,
})
}
c.JSON(http.StatusOK, simplified)
})
projects.POST("", func(c *gin.Context) { projects.POST("", func(c *gin.Context) {
var req services.CreateProjectRequest var req services.CreateProjectRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {

View File

@@ -835,12 +835,17 @@ async function loadProjectsForConfigUI() {
projectsCache = []; projectsCache = [];
projectNameByUUID = {}; projectNameByUUID = {};
try { try {
const resp = await fetch('/api/projects?status=all'); // Use /api/projects/all to get all projects without pagination
const resp = await fetch('/api/projects/all');
if (!resp.ok) return; if (!resp.ok) return;
const data = await resp.json(); const data = await resp.json();
projectsCache = (data.projects || []); // data is now a simple array of {uuid, name} objects
const allProjects = Array.isArray(data) ? data : (data.projects || []);
projectsCache.forEach(project => { // For compatibility with rest of code, populate projectsCache but mainly use projectNameByUUID
projectsCache = allProjects;
allProjects.forEach(project => {
projectNameByUUID[project.uuid] = project.name; projectNameByUUID[project.uuid] = project.name;
}); });