defmodule LivebookWeb.Hub.NewLive do use LivebookWeb, :live_view alias Livebook.Teams alias Livebook.Teams.Org alias LivebookWeb.LayoutComponents 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 socket = assign(socket, selected_option: "new-org", page_title: "Workspace - Livebook", 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(assigns) do ~H""" Beware! You are running Livebook in development but this page communicates with production servers.

Livebook Teams enables you to deploy internal tools built with Elixir and Livebook to your own infrastructure. It is currently in closed beta.

To create a Teams organization, you must join the beta for free early access.

    <.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} for={@form} id={"#{@selected_option}-form"} class="flex flex-col space-y-4" phx-submit="save" phx-change="validate" >
<.text_field field={f[:name]} label="Organization name" autofocus /> <.emoji_field field={f[:emoji]} label="Emoji" />
<.password_field :if={@selected_option == "join-org"} field={f[:teams_key]} label="Livebook Teams key" />
<.button :if={!@requested_code} phx-disable-with="Loading..."> {@button_label}
{@request_code_info}
1. Copy the code:
<.copyclip content={@org.user_code} />
2. Sign in to Livebook Teams and paste the code:
<.button color="gray" outlined href={@verification_uri} target="_blank"> Go to Teams
""" end defp copyclip(assigns) do ~H"""
<.icon_button class="invisible"> <.remix_icon icon="clipboard-line" />
{@content}
<.icon_button phx-click={JS.dispatch("lb:clipcopy", to: "#clipboard-code")} type="button"> <.remix_icon icon="clipboard-line" />
""" 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} -> {: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, "Workspace added successfully") |> push_navigate(to: ~p"/hub/#{hub.id}?show-key=confirm")} {: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