From 82e03978b89938219958032efb1448cc76baa181 Mon Sep 17 00:00:00 2001 From: Saumit Date: Sat, 27 Sep 2025 02:14:26 +0530 Subject: Initial snapshot - OpenTelemetry demo 2.1.3 -f --- .../lib/flagd_ui_web/components/core_components.ex | 464 +++++++++++++++++++++ .../lib/flagd_ui_web/components/layouts.ex | 111 +++++ .../flagd_ui_web/components/layouts/root.html.heex | 34 ++ src/flagd-ui/lib/flagd_ui_web/components/navbar.ex | 41 ++ .../lib/flagd_ui_web/controllers/error_html.ex | 27 ++ .../lib/flagd_ui_web/controllers/error_json.ex | 24 ++ .../flagd_ui_web/controllers/feature_controller.ex | 12 + .../lib/flagd_ui_web/controllers/page_html.ex | 13 + src/flagd-ui/lib/flagd_ui_web/endpoint.ex | 50 +++ src/flagd-ui/lib/flagd_ui_web/gettext.ex | 28 ++ .../lib/flagd_ui_web/live/advanced_editor.ex | 92 ++++ src/flagd-ui/lib/flagd_ui_web/live/dashboard.ex | 66 +++ src/flagd-ui/lib/flagd_ui_web/router.ex | 50 +++ src/flagd-ui/lib/flagd_ui_web/telemetry.ex | 73 ++++ 14 files changed, 1085 insertions(+) create mode 100644 src/flagd-ui/lib/flagd_ui_web/components/core_components.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/components/layouts.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/components/layouts/root.html.heex create mode 100644 src/flagd-ui/lib/flagd_ui_web/components/navbar.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/controllers/error_html.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/controllers/error_json.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/controllers/feature_controller.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/controllers/page_html.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/endpoint.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/gettext.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/live/advanced_editor.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/live/dashboard.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/router.ex create mode 100644 src/flagd-ui/lib/flagd_ui_web/telemetry.ex (limited to 'src/flagd-ui/lib/flagd_ui_web') diff --git a/src/flagd-ui/lib/flagd_ui_web/components/core_components.ex b/src/flagd-ui/lib/flagd_ui_web/components/core_components.ex new file mode 100644 index 0000000..5792123 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/components/core_components.ex @@ -0,0 +1,464 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as tables, forms, and + inputs. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The foundation for styling is Tailwind CSS, a utility-first CSS framework, + augmented with daisyUI, a Tailwind CSS plugin that provides UI components + and themes. Here are useful references: + + * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get + started and see the available components. + + * [Tailwind CSS](https://tailwindcss.com) - the foundational framework + we build on. You will use it for layout, sizing, flexbox, grid, and + spacing. + + * [Heroicons](https://heroicons.com) - see `icon/1` for usage. + + * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) - + the component system used by Phoenix. Some components, such as `<.link>` + and `<.form>`, are defined there. + + """ + use Phoenix.Component + use Gettext, backend: FlagdUiWeb.Gettext + + alias Phoenix.LiveView.JS + + @doc """ + Renders flash notices. + + ## Examples + + <.flash kind={:info} flash={@flash} /> + <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back! + """ + attr :id, :string, doc: "the optional id of flash container" + attr :flash, :map, default: %{}, doc: "the map of flash messages to display" + attr :title, :string, default: nil + attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup" + attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container" + + slot :inner_block, doc: "the optional inner block that renders the flash message" + + def flash(assigns) do + assigns = assign_new(assigns, :id, fn -> "flash-#{assigns.kind}" end) + + ~H""" +
hide("##{@id}")} + role="alert" + class="toast toast-top toast-end z-50" + {@rest} + > +
+ <.icon :if={@kind == :info} name="hero-information-circle-mini" class="size-5 shrink-0" /> + <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="size-5 shrink-0" /> +
+

{@title}

+

{msg}

+
+
+ +
+
+ """ + end + + @doc """ + Renders a button with navigation support. + + ## Examples + + <.button>Send! + <.button phx-click="go" variant="primary">Send! + <.button navigate={~p"/"}>Home + """ + attr :rest, :global, include: ~w(href navigate patch) + attr :variant, :string, values: ~w(primary) + slot :inner_block, required: true + + def button(%{rest: rest} = assigns) do + variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} + assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant])) + + if rest[:href] || rest[:navigate] || rest[:patch] do + ~H""" + <.link class={["btn", @class]} {@rest}> + {render_slot(@inner_block)} + + """ + else + ~H""" + + """ + end + end + + @doc """ + Renders an input with label and error messages. + + A `Phoenix.HTML.FormField` may be passed as argument, + which is used to retrieve the input name, id, and values. + Otherwise all attributes may be passed explicitly. + + ## Types + + This function accepts all HTML input types, considering that: + + * You may also set `type="select"` to render a ` + + {@label} + + + <.error :for={msg <- @errors}>{msg} + + """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # Helper used by inputs to generate form errors + defp error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle-mini" class="size-5" /> + {render_slot(@inner_block)} +

+ """ + end + + @doc """ + Renders a header with title. + """ + attr :class, :string, default: nil + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ {render_slot(@inner_block)} +

+

+ {render_slot(@subtitle)} +

+
+
{render_slot(@actions)}
+
+ """ + end + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id">{user.id} + <:col :let={user} label="username">{user.username} + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" + + + + + + + + + + + + + +
{col[:label]} + {gettext("Actions")} +
+ {render_slot(col, @row_item.(row))} + +
+ <%= for action <- @action do %> + {render_slot(action, @row_item.(row))} + <% end %> +
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title">{@post.title} + <:item title="Views">{@post.views} + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" + + """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in `assets/vendor/heroicons.js`. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :string, default: "size-4" + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(FlagdUiWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(FlagdUiWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/components/layouts.ex b/src/flagd-ui/lib/flagd_ui_web/components/layouts.ex new file mode 100644 index 0000000..164da11 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/components/layouts.ex @@ -0,0 +1,111 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Layouts do + @moduledoc """ + This module holds different layouts used by your application. + + See the `layouts` directory for all templates available. + The "root" layout is a skeleton rendered as part of the + application router. The "app" layout is rendered as component + in regular views and live views. + """ + use FlagdUiWeb, :html + + embed_templates "layouts/*" + + def app(assigns) do + ~H""" + + +
+
+ {render_slot(@inner_block)} +
+
+ + <.flash_group flash={@flash} /> + """ + end + + @doc """ + Shows the flash group with standard titles and content. + + ## Examples + + <.flash_group flash={@flash} /> + """ + attr :flash, :map, required: true, doc: "the map of flash messages" + attr :id, :string, default: "flash-group", doc: "the optional id of flash container" + + def flash_group(assigns) do + ~H""" +
+ <.flash kind={:info} flash={@flash} /> + <.flash kind={:error} flash={@flash} /> + + <.flash + id="client-error" + kind={:error} + title={gettext("We can't find the internet")} + phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Attempting to reconnect")} + <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 motion-safe:animate-spin" /> + + + <.flash + id="server-error" + kind={:error} + title={gettext("Something went wrong!")} + phx-disconnected={show(".phx-client-error #client-error") |> JS.remove_attribute("hidden")} + phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})} + hidden + > + {gettext("Hang in there while we get back on track")} + <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 motion-safe:animate-spin" /> + +
+ """ + end + + @doc """ + Provides dark vs light theme toggle based on themes defined in app.css. + + See in root.html.heex which applies the theme before page load. + """ + def theme_toggle(assigns) do + ~H""" +
+
+ + + + + + +
+ """ + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/components/layouts/root.html.heex b/src/flagd-ui/lib/flagd_ui_web/components/layouts/root.html.heex new file mode 100644 index 0000000..010a552 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/components/layouts/root.html.heex @@ -0,0 +1,34 @@ + + + + + + + + <.live_title default="Flagd-ui"> + {assigns[:page_title]} + + + + + + + {@inner_content} + + diff --git a/src/flagd-ui/lib/flagd_ui_web/components/navbar.ex b/src/flagd-ui/lib/flagd_ui_web/components/navbar.ex new file mode 100644 index 0000000..b224f51 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/components/navbar.ex @@ -0,0 +1,41 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Components.Navbar do + use Phoenix.Component + use FlagdUiWeb, :live_view + + attr :mode, :string, default: "basic", doc: "the view currently displaying" + + def navbar(assigns) do + ~H""" + + """ + end + + defp classes(route, route), + do: + "rounded-md px-3 py-2 text-sm font-medium bg-blue-700 text-white underline underline-offset-4 transition-all duration-200" + + defp classes(_, _), + do: + "rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-700 hover:text-white transition-all duration-200" +end diff --git a/src/flagd-ui/lib/flagd_ui_web/controllers/error_html.ex b/src/flagd-ui/lib/flagd_ui_web/controllers/error_html.ex new file mode 100644 index 0000000..3440c0d --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/controllers/error_html.ex @@ -0,0 +1,27 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.ErrorHTML do + @moduledoc """ + This module is invoked by your endpoint in case of errors on HTML requests. + + See config/config.exs. + """ + use FlagdUiWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/flagd_ui_web/controllers/error_html/404.html.heex + # * lib/flagd_ui_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/controllers/error_json.ex b/src/flagd-ui/lib/flagd_ui_web/controllers/error_json.ex new file mode 100644 index 0000000..a726099 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/controllers/error_json.ex @@ -0,0 +1,24 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.ErrorJSON do + @moduledoc """ + This module is invoked by your endpoint in case of errors on JSON requests. + + See config/config.exs. + """ + + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/controllers/feature_controller.ex b/src/flagd-ui/lib/flagd_ui_web/controllers/feature_controller.ex new file mode 100644 index 0000000..551f225 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/controllers/feature_controller.ex @@ -0,0 +1,12 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.FeatureController do + use FlagdUiWeb, :controller + + def read(conn, _params) do + %{"flags" => flags} = GenServer.call(Storage, :read) + + json(conn, %{"flags" => flags}) + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/controllers/page_html.ex b/src/flagd-ui/lib/flagd_ui_web/controllers/page_html.ex new file mode 100644 index 0000000..fc14663 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/controllers/page_html.ex @@ -0,0 +1,13 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.PageHTML do + @moduledoc """ + This module contains pages rendered by PageController. + + See the `page_html` directory for all templates available. + """ + use FlagdUiWeb, :html + + embed_templates "page_html/*" +end diff --git a/src/flagd-ui/lib/flagd_ui_web/endpoint.ex b/src/flagd-ui/lib/flagd_ui_web/endpoint.ex new file mode 100644 index 0000000..5f4d633 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/endpoint.ex @@ -0,0 +1,50 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :flagd_ui + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_flagd_ui_key", + signing_salt: "ZIW3lwrD", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # When code reloading is disabled (e.g., in production), + # the `gzip` option is enabled to serve compressed + # static files generated by running `phx.digest`. + plug Plug.Static, + at: "/", + from: :flagd_ui, + gzip: not code_reloading?, + only: FlagdUiWeb.static_paths() + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug FlagdUiWeb.Router + + def get_root_path, do: config(:url) |> Enum.find(fn {k, _} -> k == :path end) |> elem(1) +end diff --git a/src/flagd-ui/lib/flagd_ui_web/gettext.ex b/src/flagd-ui/lib/flagd_ui_web/gettext.ex new file mode 100644 index 0000000..ac82fe7 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/gettext.ex @@ -0,0 +1,28 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations + that you can use in your application. To use this Gettext backend module, + call `use Gettext` and pass it as an option: + + use Gettext, backend: FlagdUiWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext.Backend, otp_app: :flagd_ui +end diff --git a/src/flagd-ui/lib/flagd_ui_web/live/advanced_editor.ex b/src/flagd-ui/lib/flagd_ui_web/live/advanced_editor.ex new file mode 100644 index 0000000..fbecaa8 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/live/advanced_editor.ex @@ -0,0 +1,92 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.AdvancedEditor do + use FlagdUiWeb, :live_view + + alias FlagdUiWeb.CoreComponents + alias FlagdUiWeb.Components.Navbar + + def mount(_, _, socket) do + state = GenServer.call(Storage, :read) + content = Jason.encode!(state, pretty: true) + + {:ok, + socket + |> assign(content: content) + |> assign(unsaved_changes: false)} + end + + def render(assigns) do + ~H""" +
+ + + + + +
+ <.form for={%{}}> + +
+ +

Unsaved changes

+
+ +
+
+ """ + end + + def handle_event("edit", payload, socket) do + %{"content" => content} = payload + + {:noreply, + socket + |> assign(content: content) + |> assign(unsaved_changes: true)} + end + + def handle_event( + "save", + _, + %{ + assigns: %{ + content: content + } + } = socket + ) do + new_socket = + case Jason.decode(content) do + {:ok, _} -> + trimmed_content = String.trim(content) + + GenServer.cast(Storage, {:replace, trimmed_content}) + + socket + |> assign(unsaved_changes: false) + |> assign(content: trimmed_content) + |> clear_flash() + |> put_flash(:info, "Saved!") + + {:error, _} -> + put_flash(socket, :error, "Invalid JSON") + end + + {:noreply, new_socket} + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/live/dashboard.ex b/src/flagd-ui/lib/flagd_ui_web/live/dashboard.ex new file mode 100644 index 0000000..cacd1d8 --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/live/dashboard.ex @@ -0,0 +1,66 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Dashboard do + use FlagdUiWeb, :live_view + + alias FlagdUiWeb.CoreComponents + alias FlagdUiWeb.Components.Navbar + + def mount(_, _, socket) do + %{"flags" => flags} = GenServer.call(Storage, :read) + {:ok, socket |> assign(:flags, flags)} + end + + def render(assigns) do + ~H""" +
+ + + + + + <.form for={@flags}> +
+
+
+
+

{name}

+

{data["description"]}

+
+
+
+ +
+
+
+
+
+ +
+ """ + end + + def handle_event("flag_changed", payload, socket) do + %{"_target" => [target]} = payload + variant = payload[target] + + GenServer.cast(Storage, {:write, target, variant}) + + new_socket = put_flash(socket, :info, "Saved: #{target}") + + {:noreply, new_socket} + end + + defp get_variants(%{"variants" => variants}), do: Enum.map(variants, fn {key, _} -> key end) + defp get_variants(_), do: [] +end diff --git a/src/flagd-ui/lib/flagd_ui_web/router.ex b/src/flagd-ui/lib/flagd_ui_web/router.ex new file mode 100644 index 0000000..df7d8ba --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/router.ex @@ -0,0 +1,50 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Router do + use FlagdUiWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {FlagdUiWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", FlagdUiWeb do + pipe_through :browser + + live "/", Dashboard + live "/advanced", AdvancedEditor + end + + # Other scopes may use custom stacks. + scope "/api", FlagdUiWeb do + pipe_through :api + + get "/read", FeatureController, :read + end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:flagd_ui, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: FlagdUiWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/src/flagd-ui/lib/flagd_ui_web/telemetry.ex b/src/flagd-ui/lib/flagd_ui_web/telemetry.ex new file mode 100644 index 0000000..8afe61f --- /dev/null +++ b/src/flagd-ui/lib/flagd_ui_web/telemetry.ex @@ -0,0 +1,73 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +defmodule FlagdUiWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.start.system_time", + unit: {:native, :millisecond} + ), + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.start.system_time", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.exception.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.socket_connected.duration", + unit: {:native, :millisecond} + ), + sum("phoenix.socket_drain.count"), + summary("phoenix.channel_joined.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_handled_in.duration", + tags: [:event], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {FlagdUiWeb, :count_users, []} + ] + end +end -- cgit v1.2.3