Files
summercms/modules/cabana/backend_screens.go
Jakub Zych 7340241f22 feat(19.6.1-01): Administrators screen and cross-controller record actions
- pact.HasAdminActionExtensions / AdminActionExtension: plugins add record
  actions to any admin controller by ID, merged at boot before permission
  validation (unknown target, no preview, duplicate name stop the boot)
- internal summercms.cabana pseudo-plugin, appended only when some plugin
  registers admin controllers: Administrators list and preview, settings
  link under System, backend.manage_users and backend.manage_roles with no
  role grants
- backend::lang user, user.role and permissions copy (en, pl)
- smoke tests: compile-time seam and settings, Postgres list/preview/action
2026-10-22 10:59:23 +02:00

130 lines
4.6 KiB
Go

package cabana
import (
"embed"
"io/fs"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
)
// The admin's own screens for backend administrators and roles (Settings →
// System), registered by cabana itself through an internal pseudo-plugin.
const (
// PermissionManageUsers opens the Administrators screen. No role is
// granted it in code: until an administrator grants it, only superusers
// reach the screen.
PermissionManageUsers = "backend.manage_users"
// PermissionManageRoles opens the Roles screen. No role is granted it in
// code either.
PermissionManageRoles = "backend.manage_roles"
// AdministratorsControllerID is the admin controller ID of the
// Administrators screen, the target other plugins name to add record
// actions to it (pact.HasAdminActionExtensions).
AdministratorsControllerID = "summercms.cabana.users"
// RolesControllerID is the admin controller ID of the Roles screen.
RolesControllerID = "summercms.cabana.roles"
)
// backendScreensPluginID is the pseudo-plugin's ID. It is also the
// migration history ID of the backend tables (lagoon), which is untouched:
// the pseudo-plugin declares no migrations.
const backendScreensPluginID = "summercms.cabana"
// backendScreensFS holds the screens' admin YAML and partials. all: keeps
// the partials, whose names start with an underscore.
//
//go:embed all:backendscreens
var backendScreensFS embed.FS
// backendScreens is the internal pseudo-plugin that registers the
// Administrators and Roles screens, their two permissions and their settings
// entries. Activate appends it to the plugins only when some plugin
// registers admin controllers, so an application without an admin still
// boots without one. app is the activating application and reg the compiled
// registry, set once compilation is done; the controllers read both per
// request.
type backendScreens struct {
app *backpack.App
reg *Registry
}
var (
_ party.Plugin = (*backendScreens)(nil)
_ pact.AdminAssets = (*backendScreens)(nil)
_ pact.HasAdminControllers = (*backendScreens)(nil)
_ pact.HasPermissions = (*backendScreens)(nil)
_ pact.HasSettings = (*backendScreens)(nil)
)
func (*backendScreens) ID() string { return backendScreensPluginID }
func (*backendScreens) Requires() []string { return nil }
func (*backendScreens) Register(*backpack.App) error { return nil }
func (*backendScreens) Boot(*backpack.App) error { return nil }
// AdminFS serves the embedded tree rooted at backendscreens/, so its paths
// start at controllers/ and models/.
func (*backendScreens) AdminFS() fs.FS {
sub, err := fs.Sub(backendScreensFS, "backendscreens")
if err != nil {
return nil
}
return sub
}
func (s *backendScreens) AdminControllers() []pact.AdminController {
return []pact.AdminController{usersAdmin{screens: s}}
}
// Permissions declares the two screen permissions with no role grants
// (D-04): an upgrade gives no existing role access to the screens.
func (*backendScreens) Permissions() []pact.Permission {
return []pact.Permission{
{Code: PermissionManageUsers, Tab: "backend::lang.permissions.tab_system", Label: "backend::lang.permissions.manage_users"},
{Code: PermissionManageRoles, Tab: "backend::lang.permissions.tab_system", Label: "backend::lang.permissions.manage_roles"},
}
}
// Settings links the screens from Settings → System; there is no navigation
// entry.
func (*backendScreens) Settings() []pact.SettingsItem {
return []pact.SettingsItem{
{
Code: "administrators",
Label: "backend::lang.user.menu_label",
Description: "backend::lang.user.menu_description",
Category: "backend::lang.settings.category_system",
Icon: "users",
Order: 100,
Permissions: []string{PermissionManageUsers},
Controller: AdministratorsControllerID,
},
}
}
// application is the activating app, or nil.
func (s *backendScreens) application() *backpack.App {
if s == nil {
return nil
}
return s.app
}
// registry is the compiled registry, or nil before compilation finished.
func (s *backendScreens) registry() *Registry {
if s == nil {
return nil
}
return s.reg
}
// withBackendScreens returns a new plugin slice with the pseudo-plugin
// appended, and the pseudo-plugin. plugins itself is never modified.
func withBackendScreens(app *backpack.App, plugins []party.Plugin) ([]party.Plugin, *backendScreens) {
screens := &backendScreens{app: app}
out := make([]party.Plugin, 0, len(plugins)+1)
out = append(out, plugins...)
return append(out, screens), screens
}