Whether a site is up, and who may reach it when it is not.
A site is unavailable in two ways:
| state | set on | who may reach it |
|---|---|---|
:inactive | Admin → Runtime → Sites | a master admin of the surface asked about |
:offline | Settings → "Take this site offline" (the site's own or the global switch) | its own admins and master admins of the surface asked about |
A site the site cache does not know — archived, deleted, or not loaded — is :inactive, so a gate
asking about it closes rather than opens.
Every answer is read from ETS: MishkaCmsCore.Runtime.States.SiteState for active,
MishkaCmsCore.Settings.offline?/1 for the switch, and the permissions the user was loaded with. No
query, no process call, so it can sit on every request.
An admin is an admin of a surface: the screens (:dashboard, the default) or the admin JSON:API
and GraphQL (:api), or :any for either one. See MishkaCmsCore.Accounts.Helper.master_admin?/2
and MishkaCmsCore.Accounts.Helper.site_admin?/3 for who is who.
Open LiveViews
on_mount/4 closes an open LiveView the moment its site closes to its user: on a live navigation,
on every event, and when changed/1 announces that the site's state changed. Add it to every
LiveView:
on_mount MishkaCmsCore.Runtime.SiteAvailabilityA view on a sign-in address (MishkaCmsCore.Runtime.Plugs.SiteLookupPlug.sign_in_path?/2, read
from the address of every navigation) stays open while its site is offline, so the site's admins
can sign in; every view closes on an inactive site. A refused view is redirected to the address it
was on, a full page load that the pipeline answers.
A view that shows a record of another site — a preview, opened on any address — follows that site
too, with follow/3 from its mount/3: it is refused there, and closed later, by the rule
allowed?/2 answers for that site.
A view whose viewer the site's closed sign-in signs out (MishkaCmsCore.Accounts.AuthGate.signs_out?/2)
reloads the address it was on the same way, and the pipeline signs them out there. The gate's
settings call changed/1 as the offline switch does.
Events
runtime:state:site:availability_changed—%{key: site_id},nilfor every site, each timechanged/1is called.web:live:site_unavailable— the socket an open LiveView is about to be sent away with, with%{site_id: site_id, path: path}as its meta. A plugin may redirect it elsewhere.
Summary
Functions
Whether user may reach the site site_id through surface — :dashboard (the default) for its
pages, files, previews and screens, :api for its admin APIs, :any for either.
allowed?/3 for a state already read with state/1.
Tells this node's open LiveViews that site_id's state may have changed, and disconnects this
node's sockets bound to it (socket_id/1) when it is no longer open; nil for every site.
Makes an open LiveView follow site_id as well as its host's site, from its mount/3.
Closes an open LiveView the moment its site closes to its user. See the moduledoc.
The ids of every site that is up: active, and not offline by its own or the global switch.
The id a socket bound to site_id answers to from Phoenix.Socket.id/1, as the GraphQL
socket's does. changed/1 disconnects every socket under it once the site is no longer open, and
each one that reconnects is asked again.
The site's state by its id.
Types
Functions
@spec allowed?( String.t() | nil, map() | nil, MishkaCmsCore.Accounts.Helper.admin_surface() ) :: boolean()
Whether user may reach the site site_id through surface — :dashboard (the default) for its
pages, files, previews and screens, :api for its admin APIs, :any for either.
nil for user is a visitor who is not signed in.
SiteAvailability.allowed?(site.id, user)
SiteAvailability.allowed?(site.id, user, :api)
@spec allowed_in?( state(), String.t() | nil, map() | nil, MishkaCmsCore.Accounts.Helper.admin_surface() ) :: boolean()
allowed?/3 for a state already read with state/1.
@spec changed(String.t() | nil) :: :ok
Tells this node's open LiveViews that site_id's state may have changed, and disconnects this
node's sockets bound to it (socket_id/1) when it is no longer open; nil for every site.
SiteState and SettingsState call it after writing, so a view that checks again reads the new
state. Local to the node, because each node's caches call it for their own write.
@spec follow(Phoenix.LiveView.Socket.t(), String.t() | nil, map()) :: {:ok, Phoenix.LiveView.Socket.t()} | {:error, String.t() | nil, :offline | :inactive}
Makes an open LiveView follow site_id as well as its host's site, from its mount/3.
{:error, site_id, state} — the site that is closed to the viewer, the host's or site_id — when
either is closed now; otherwise the view is closed as soon as one is. nil — a global record —
follows the host's site only. Needs on_mount/4.
Closes an open LiveView the moment its site closes to its user. See the moduledoc.
@spec open_site_ids() :: [String.t()]
The ids of every site that is up: active, and not offline by its own or the global switch.
The id a socket bound to site_id answers to from Phoenix.Socket.id/1, as the GraphQL
socket's does. changed/1 disconnects every socket under it once the site is no longer open, and
each one that reconnects is asked again.
Needs the endpoint's PubSub server to be MishkaCmsCore.PubSub.
The site's state by its id.
SiteAvailability.state(site.id)
#=> :offline