MishkaCmsCoreWeb. Live. Admin. Dashboard. Runtime. SettingsLive
(Mishka CMS Core v0.0.1)
Copy Markdown
View Source
The CMS settings admin page: a left rail of category tabs and a content panel whose fields are
rendered dynamically from MishkaCmsCore.Settings definitions, one input per setting by its
type. The markup lives in the co-located settings_live.html.heex.
Settings are multitenant (see MishkaCmsCore.Runtime.Setting). A master admin picks a scope —
"Global" (the value for every site) or a specific site — from the header; a tenant admin is
locked to their own site. Each setting declares a scope: :global settings are editable only in
the Global view (shown read-only to a site), :master settings are Global-only and hidden from a
site entirely, :site settings only in a site view, and :both (a global default a site may
override) in either, and one declared editable?: false is read-only to everyone. A setting is
shown to whoever may write it (MishkaCmsCore.Settings.writable?/2): its declared permission, or
admin:dashboard:setting when it declares none. A tab left with nothing to show is not drawn, so a
dashboard admin without the setting area sees their own Preferences alone.
Saving goes through Settings.put_as/4, which carries the admin as the Ash actor so
MishkaCmsCore.Runtime.Setting's own policies decide which site's row they may write. Scope is
encoded as nil for the Global view and a site uuid for a specific site's view, and write_scope/1
clamps a tenant to their own site — but that clamp is a convenience, not the boundary: a submitted
scope that is not theirs is refused by the resource whether or not the page catches it first.
A save only writes what actually changed, and only fields the browser sent (an unticked checkbox being the one thing whose absence is meaningful), so editing one input on a crowded tab costs one write and one broadcast instead of one per field on the tab.
The page is live for everyone watching it: MishkaCmsCore.Runtime.Setting publishes every write to
"setting:updated", which the page subscribes to. A master acts on all of it; a tenant discards
the writes belonging to sites other than its own (reaches?/2), so a neighbour's save never moves
their page. A broadcast is also ignored while the viewer is mid-edit (dirty?), so someone else's
save never clobbers unsaved input.
Settings are drawn in the order they are declared. A named :group is one section, where its first
setting stands; settings with no group form runs between the named groups, which have no heading.
A group whose settings declare collapsed: true (MishkaCmsCore.Settings.define/3) is drawn as a
closed accordion titled by the group, whatever its settings hold. It opens by itself only when a
setting in it was refused by the last save (open_group?/2), so the message under the field is
in view, and once drawn it stays as the admin left it, through every later change of the page. A
refused value is said under its field, whatever the tab.
A :secret setting (a provider's API key) is never drawn back: its field is empty, says the
last four characters of what is stored, and a save that leaves it empty keeps the stored one. The
Email tab adds a test email to any address, through the scope's provider, and the email history
(MishkaCmsCore.Mail.Delivery).
The SEO tab adds the Redraw share cards buttons (MishkaCmsCoreWeb.Live.Admin.Components.ShareCardRedraw)
for whoever may change the site's logo: every share card of the scope — every active site in
Global — and each producer's own, one row for each producer that answers
MishkaCmsCore.Runtime.ShareCard.targets_event/0.
See MishkaCmsCore.Settings and MishkaCmsCore.Runtime.Setting.
Summary
Functions
A setting changed somewhere. The global setting:updated topic carries EVERY site's writes, so a
tenant drops the ones outside its reach (reaches?/2) rather than re-reading for a site it cannot
see. A refresh is also skipped mid-edit so a broadcast never clobbers unsaved input.
Callback implementation for Phoenix.LiveView.render/1.
Functions
A setting changed somewhere. The global setting:updated topic carries EVERY site's writes, so a
tenant drops the ones outside its reach (reaches?/2) rather than re-reading for a site it cannot
see. A refresh is also skipped mid-edit so a broadcast never clobbers unsaved input.
Callback implementation for Phoenix.LiveView.render/1.