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 {:ok, socket |> assign( page_title: "Workspace - Livebook", requested_code: false, org: nil, verification_uri: nil, form: nil, button_label: nil, request_code_info: nil ) |> assign_form()} 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 notebooks as internal apps or turn Livebook into a controlled environment for runbooks and production operations.

To use it, you need to join a Teams organization.

  • <.remix_icon icon="organization-chart" class="group-hover:text-blue-600 text-lg text-blue-600" /> Join an existing organization
<.form :let={f} for={@form} id="join-org-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 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 @impl true 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 case Teams.join_org(socket.assigns.org, attrs) 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) 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, %Ecto.Changeset{} = changeset) do assign(socket, form: to_form(changeset)) end defp random_emoji do Enum.random(~w[💡 🚀 🌈 🦄 🐱 👩‍💻 ⚽️ ⭐️]) end end