Customizable dashboards for PhoenixKit.
Lets users compose dashboard pages from widgets contributed by any
PhoenixKit module. A widget is a self-contained Phoenix.LiveComponent; a
dashboard is a free-form 2D grid of placed widget instances, each with its own
size, position, and settings. Layouts are persisted per user (personal
dashboards) and per system/role (shared dashboards).
Architecture
PhoenixKitDashboards.Widget— the widget type struct + the plain-map provider contract (phoenix_kit_widgets/0).PhoenixKitDashboards.Registry— runtime discovery + cached catalog (the union of every module'sphoenix_kit_widgets/0, including this one's), filtered by module enablement and permissions.PhoenixKitDashboards.Widgets.*— this module's built-in widgets (note, clock, module-stats), exposed viaphoenix_kit_widgets/0like any other provider.PhoenixKitDashboards.Schemas.Dashboard+PhoenixKitDashboards.Dashboards— the dashboard schema (JSONBlayout) and its context. The backing table (phoenix_kit_dashboards) is created by core's versioned migrationV133— modules do no DDL of their own.PhoenixKitDashboards.Web.*— the manage + builder LiveViews.
Exposing widgets from another module
Any module can contribute widgets by defining a zero-arity
phoenix_kit_widgets/0 returning plain maps — no dependency on this package:
def phoenix_kit_widgets do
[%{key: "emails.deliverability", name: "Deliverability",
module_key: "emails", component: PhoenixKitEmails.Widgets.DeliverabilityLive,
default_size: %{w: 6, h: 2}}]
endSee PhoenixKitDashboards.Widget for the full contract.
Summary
Functions
Project-extension catalog entry for the phoenix_kit_projects hub —
duck-typed contract (no dependency on that package, no @impl): a
read-only Dashboard tab rendering one linked SHARED dashboard
(config["dashboard_uuid"], picked from project_dashboard_options/0).
This module's own widgets, exposed through the same phoenix_kit_widgets/0
contract every other module uses (see the "Exposing widgets" section above), so
the Registry discovers them uniformly — the built-ins are a live worked example
of the contract, not a special-cased internal path.
Options for the project-extension config picker: every SHARED
(scope == "system") dashboard, as %{value:, label:} — the only scope
a project-wide tab may render (personal/role dashboards carry per-user
visibility). [] when the table is unavailable.
Pins every label this module declares as a gettext msgid.
Functions
@spec phoenix_kit_project_extensions() :: [map()]
Project-extension catalog entry for the phoenix_kit_projects hub —
duck-typed contract (no dependency on that package, no @impl): a
read-only Dashboard tab rendering one linked SHARED dashboard
(config["dashboard_uuid"], picked from project_dashboard_options/0).
@spec phoenix_kit_widgets() :: [map()]
This module's own widgets, exposed through the same phoenix_kit_widgets/0
contract every other module uses (see the "Exposing widgets" section above), so
the Registry discovers them uniformly — the built-ins are a live worked example
of the contract, not a special-cased internal path.
Options for the project-extension config picker: every SHARED
(scope == "system") dashboard, as %{value:, label:} — the only scope
a project-wide tab may render (personal/role dashboards carry per-user
visibility). [] when the table is unavailable.
@spec translatable_labels() :: [String.t()]
Pins every label this module declares as a gettext msgid.
permission_metadata/0 and admin_tabs/0 declare their labels as plain
string literals, so mix gettext.extract never sees this file —
PhoenixKit.Dashboard.Tab.localized_label/1 (and the permissions matrix'
PhoenixKit.Users.Permissions.localized_module_label/1) translate them at
render time through each declaration's gettext_backend/gettext_domain,
but only if the msgid already exists in this module's own catalogue.
Without this anchor, "Dashboards" happens to also appear as a literal
gettext("Dashboards") call in Web.DashboardsLive's page title — so it
would stay in default.pot today, but only by coincidence (see
phoenix_kit_warehouse's translatable_labels/0, which hit exactly this
trap). The other three tab labels have no such coincidental anchor at all.
dgettext_noop/2 registers the msgid and returns it unchanged — nothing
here runs at request time.