Add embedded Vue SPA admin panel with go:embed
CI / test (push) Failing after 13m11s

This is the complete fix for the missing /admin/ route and frontend serving:

Backend:
- internal/web/web.go: new package with go:embed for web/dist/
- internal/api/router.go: add routes for /admin/, /admin/*, /assets/*
- internal/db/db.go: fix SQLite DSN parsing (sqlite:///path -> path)

Build system:
- Makefile: new 'embed-prep' target copies web/dist to internal/web/dist
- make build now runs embed-prep -> frontend/build automatically

Deployment:
- deploy/llamalink.service: remove invalid --host/--port flags,
  add EnvironmentFile=/etc/llamalink/env

Verified:
- /health returns 200
- /admin/ serves Vue SPA HTML
- /assets/* serves CSS and JS files from embedded FS
- sqlite:///./llamalink.db works correctly
This commit is contained in:
2026-07-31 14:47:15 -04:00
parent a54b134e87
commit 0d6c8c7989
18 changed files with 231 additions and 10 deletions
+30
View File
@@ -1,6 +1,8 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/llamalink/llamalink/internal/api/handlers"
"github.com/llamalink/llamalink/internal/api/middleware"
@@ -8,6 +10,7 @@ import (
"github.com/llamalink/llamalink/internal/config"
"github.com/llamalink/llamalink/internal/llama"
"github.com/llamalink/llamalink/internal/quota"
"github.com/llamalink/llamalink/internal/web"
"gorm.io/gorm"
)
@@ -73,5 +76,32 @@ func New(cfg *config.Config, db *gorm.DB, llamaManager *llama.Manager) *gin.Engi
usage.GET("", usageHandler.GetCurrentKeyUsage)
usage.GET("/:key_id", usageHandler.GetUsage)
// Admin SPA (static files with embedded frontend)
r.GET("/assets/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
data, err := web.ServeAsset(filepath)
if err != nil {
c.String(http.StatusNotFound, "asset not found")
return
}
c.Data(http.StatusOK, http.DetectContentType(data), data)
})
r.GET("/admin", func(c *gin.Context) {
index, err := web.Index()
if err != nil {
c.String(http.StatusInternalServerError, "index.html not found")
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", index)
})
r.GET("/admin/*filepath", func(c *gin.Context) {
index, err := web.Index()
if err != nil {
c.String(http.StatusInternalServerError, "index.html not found")
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", index)
})
return r
}