defmodule LivebookWeb.Hub.NewLive do use LivebookWeb, :live_view alias Livebook.Teams alias Livebook.Teams.Org alias LivebookWeb.LayoutHelpers on_mount LivebookWeb.SidebarHook @check_completion_data_interval Application.compile_env( :livebook, :check_completion_data_interval, 3000 ) @impl true def mount(_params, _session, socket) do enabled? = Livebook.Config.feature_flag_enabled?(:create_hub) socket = assign(socket, selected_option: "new-org", page_title: "Hub - Livebook", enabled?: enabled?, requested_code: false, org: nil, verification_uri: nil, form: nil, button_label: nil, request_code_info: nil ) socket = assign_form(socket, "new-org") {:ok, socket} end @impl true def render(%{enabled?: false} = assigns) do ~H"""

Livebook Teams will amplify Livebook with features designed for teams and businesses.

It will allow you to share notebooks, manage secrets, and deploy Livebook apps within your organization.

The product is still in development. We want to get feedback from beta users and understand their use cases before the public launch.

Learn more about Livebook Teams and join the beta program.

- The Livebook crew

""" end def render(assigns) do ~H"""

Manage your Livebooks in the cloud with Hubs.

    <.tab_button id="new-org" selected={@selected_option} title="Create a new organization" icon="lightbulb-flash-line" /> <.tab_button id="join-org" selected={@selected_option} title="Join an existing organization" icon="organization-chart" />
<.form :let={f} id={"#{@selected_option}-form"} class="flex flex-col space-y-4" for={@form} phx-submit="save" phx-change="validate" >
<.text_field field={f[:name]} label="Name" /> <.emoji_field field={f[:emoji]} label="Emoji" />
<.password_field :if={@selected_option == "join-org"} field={f[:teams_key]} label="Livebook Teams Key" />
<%= @request_code_info %>
1. Copy the code:
<.copyclip content={@org.user_code} />
2. Visit Livebook Teams and paste it:
""" end defp copyclip(assigns) do ~H"""
<%= @content %>
""" end defp tab_button(assigns) do ~H"""
  • """ end defp selected_tab_button(id, id), do: "border-black/10 bg-white drop-shadow-sm hover:!opacity-100" defp selected_tab_button(_, _), do: "border-transparent text-gray-500 hover:text-gray-800" @impl true def handle_event("select_option", %{"option" => option}, socket) do {:noreply, socket |> assign(selected_option: option, requested_code: false, verification_uri: nil) |> assign_form(option)} end def handle_event("validate", %{"org" => attrs}, socket) do changeset = socket.assigns.org |> Teams.change_org(attrs) |> Map.replace!(:action, :validate) {:noreply, assign_form(socket, changeset)} end def handle_event("save", %{"org" => attrs}, socket) do result = case socket.assigns.selected_option do "new-org" -> Teams.create_org(socket.assigns.org, attrs) "join-org" -> Teams.join_org(socket.assigns.org, attrs) end case result do {:ok, %{"device_code" => device_code} = response} -> attrs = Map.merge(attrs, response) changeset = Teams.change_org(socket.assigns.org, attrs) org = Ecto.Changeset.apply_action!(changeset, :insert) Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, socket |> assign(requested_code: true, org: org, verification_uri: response["verification_uri"]) |> assign_form(changeset)} {:error, changeset} -> changeset = Map.replace!(changeset, :action, :validate) {:noreply, assign_form(socket, changeset)} {:transport_error, message} -> {:noreply, put_flash(socket, :error, message)} end end @impl true def handle_info({:check_completion_data, device_code}, %{assigns: %{org: org}} = socket) do case Teams.get_org_request_completion_data(org, device_code) do {:ok, :awaiting_confirmation} -> Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, socket} {:ok, %{"id" => _id, "session_token" => _session_token} = response} -> hub = Teams.create_hub!(%{ org_id: response["id"], user_id: response["user_id"], org_key_id: response["org_key_id"], org_public_key: response["org_public_key"], session_token: response["session_token"], teams_key: org.teams_key, hub_name: org.name, hub_emoji: org.emoji }) {:noreply, socket |> put_flash(:success, "Hub added successfully") |> push_navigate(to: ~p"/hub/#{hub.id}?show-key=true")} {:error, :expired} -> changeset = org |> Teams.change_org(%{user_code: nil}) |> Map.replace!(:action, :validate) {:noreply, socket |> assign(requested_code: false, org: org, verification_uri: nil) |> put_flash(:error, "Oh no! Your org request expired, could you please try again?") |> assign_form(changeset)} {:transport_error, message} -> Process.send_after( self(), {:check_completion_data, device_code}, @check_completion_data_interval ) {:noreply, put_flash(socket, :error, message)} end end def handle_info(_any, socket), do: {:noreply, socket} defp assign_form(socket, "join-org") do org = %Org{emoji: random_emoji()} changeset = Teams.change_org(org) socket |> assign( org: org, button_label: "Join", request_code_info: "Authenticate with your organization" ) |> assign_form(changeset) end defp assign_form(socket, "new-org") do org = %Org{emoji: random_emoji(), teams_key: Org.teams_key()} changeset = Teams.change_org(org) socket |> assign( org: org, button_label: "Create", request_code_info: "Verify your new organization" ) |> assign_form(changeset) end defp assign_form(socket, %Ecto.Changeset{} = changeset) do assign(socket, form: to_form(changeset)) end defp random_emoji do Enum.random(~w[💡 🚀 🌈 🦄 🐱 👩‍💻 ⚽️ ⭐️]) end end