frontend: add admin settings page for base URL configuration

Phase 2: Create admin UI for system configuration

- Add new /admin/settings route in frontend.go (protected by AdminMiddleware)
- Create admin_settings.templ with HTMX-powered form for base URL
- Add Settings link to admin sidebar navigation
- Admin settings form submits via HTMX to PUT /api/system/config
- Success message displays after save with updated form

The settings page allows admins to configure the base URL used for
device sync URLs, OPDS endpoints, and API access.
This commit is contained in:
2026-03-11 16:41:45 -04:00
parent 5451e82b2d
commit c1b664dbe5
4 changed files with 247 additions and 2 deletions
+39 -2
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"time"
"bookhoard/internal/config"
"bookhoard/internal/database"
"bookhoard/internal/handlers"
"bookhoard/internal/services"
@@ -530,8 +531,14 @@ func registerFrontendRoutes(cfg *Config) {
pendingList = convertPending(pendingMaps)
}
// Get base URL from database config with fallback to config/env var
baseURL := config.GetBaseURL(c.Request().Context(), cfg.Queries)
if baseURL == "" {
baseURL = cfg.Cfg.BaseURL
}
var buf bytes.Buffer
err = templates.Devices(user, devices, pendingList, errorMsg, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
err = templates.Devices(user, devices, pendingList, errorMsg, baseURL).Render(c.Request().Context(), &buf)
if err != nil {
return err
}
@@ -723,6 +730,30 @@ func registerFrontendRoutes(cfg *Config) {
}))
// Admin: Get profile modal for editing user
frontendProtected.GET("/admin/settings", handlers.AdminMiddleware(func(c *echo.Context) error {
user, err := getTemplateUserWithTheme(c, cfg)
if err != nil {
return renderErrorPage(c, "Error loading user", "user_load_error")
}
// Fetch current system configuration - just base_url
baseURL := config.GetBaseURL(c.Request().Context(), cfg.Queries)
if baseURL == "" {
baseURL = cfg.Cfg.BaseURL
}
systemConfig := map[string]string{
"base_url": baseURL,
}
var buf bytes.Buffer
err = templates.AdminSettings(user, systemConfig, "").Render(c.Request().Context(), &buf)
if err != nil {
return err
}
return c.HTML(http.StatusOK, buf.String())
}))
frontendProtected.GET("/admin/users/:id/profile-modal", handlers.AdminMiddleware(func(c *echo.Context) error {
// Get target user ID from URL
targetUserID := c.Param("id")
@@ -790,8 +821,14 @@ func registerFrontendRoutes(cfg *Config) {
pendingList = convertPending(pendingMaps)
}
// Get base URL from database config with fallback to config/env var
baseURL := config.GetBaseURL(c.Request().Context(), cfg.Queries)
if baseURL == "" {
baseURL = cfg.Cfg.BaseURL
}
var buf bytes.Buffer
err = templates.Devices(user, devices, pendingList, errorMsg, cfg.Cfg.BaseURL).Render(c.Request().Context(), &buf)
err = templates.Devices(user, devices, pendingList, errorMsg, baseURL).Render(c.Request().Context(), &buf)
if err != nil {
return err
}