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

handle_info(broadcast, socket)

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.

render(assigns)

Callback implementation for Phoenix.LiveView.render/1.