Round out the editor: head widgets, undo, group/split-column, site settings

- Head widgets are now first-class in dashboard edit mode: they get
  drag/edit/delete handles, their own SortableJS group (so they can't
  cross into columns), and the layout endpoint accepts a
  headWidgets[] sequence alongside columns[].
- New POST /edit/api/restore swaps glance.yml ↔ glance.yml.bak —
  effectively undo (and click again to redo). Surfaced as a "Restore
  previous version" button on /edit.
- group and split-column are now editable inline via a new
  list-of-widgets schema type. Each item picks a widget type from a
  dropdown (excluding group/split-column to honor the backend's
  no-nesting rule) and renders that type's schema-driven sub-form.
  Switching the type re-renders the body.
- New /edit/site-settings page with a form for branding (app name,
  logo, favicon, footer, app icon/background). POST applies via the
  same surgical yaml.Node update used for widgets. Theme/server
  intentionally still YAML for now.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
uhlwoogi
2026-04-30 20:43:52 +00:00
co-authored by Claude Opus 4.7
parent 7f36f30dad
commit e108feb74b
8 changed files with 463 additions and 27 deletions
+34
View File
@@ -14,6 +14,7 @@ var (
adminPageTemplate = mustParseTemplate("admin-page.html", "document.html", "footer.html")
adminWidgetTemplate = mustParseTemplate("admin-widget.html", "document.html", "footer.html")
adminPageSettingsTemplate = mustParseTemplate("admin-page-settings.html", "document.html", "footer.html")
adminSiteSettingsTemplate = mustParseTemplate("admin-site-settings.html", "document.html", "footer.html")
)
// allWidgetTypes mirrors the switch in newWidget(). Aliases ("stocks") omitted.
@@ -249,6 +250,19 @@ type adminTemplateData struct {
PageSettings *adminPageSettings
IsFirstPage bool
IsLastPage bool
SiteSettings *adminSiteSettings
}
type adminSiteSettings struct {
AppName string
LogoText string
LogoURL string
FaviconURL string
AppIconURL string
AppBackgroundColor string
HideFooter bool
CustomFooter string
}
func (a *application) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
@@ -383,6 +397,26 @@ func (a *application) handleAdminWidget(w http.ResponseWriter, r *http.Request)
})
}
func (a *application) handleAdminSiteSettings(w http.ResponseWriter, r *http.Request) {
if !a.adminAccessAllowed(w, r) {
return
}
b := a.Config.Branding
settings := &adminSiteSettings{
AppName: b.AppName,
LogoText: b.LogoText,
LogoURL: b.LogoURL,
FaviconURL: b.FaviconURL,
AppIconURL: b.AppIconURL,
AppBackgroundColor: b.AppBackgroundColor,
HideFooter: b.HideFooter,
CustomFooter: string(b.CustomFooter),
}
data := adminTemplateData{App: a, SiteSettings: settings}
a.populateTemplateRequestData(&data.Request, r)
renderAdminTemplate(w, adminSiteSettingsTemplate, data)
}
func (a *application) handleAdminPageSettings(w http.ResponseWriter, r *http.Request) {
if !a.adminAccessAllowed(w, r) {
return